org.trellisldp.api.JoiningResourceServiceTest.java Source code

Java tutorial

Introduction

Here is the source code for org.trellisldp.api.JoiningResourceServiceTest.java

Source

/*
 * 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 org.trellisldp.api;

import static java.time.Instant.now;
import static java.util.Collections.emptySet;
import static java.util.Collections.synchronizedMap;
import static java.util.Optional.empty;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.concurrent.CompletableFuture.runAsync;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.trellisldp.api.Resource.SpecialResources.MISSING_RESOURCE;

import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.stream.Stream;

import org.apache.commons.rdf.api.BlankNodeOrIRI;
import org.apache.commons.rdf.api.Dataset;
import org.apache.commons.rdf.api.IRI;
import org.apache.commons.rdf.api.Quad;
import org.apache.commons.rdf.api.RDFTerm;
import org.junit.jupiter.api.Test;
import org.trellisldp.api.JoiningResourceService.RetrievableResource;
import org.trellisldp.vocabulary.LDP;

public class JoiningResourceServiceTest {

    private static final IRI testResourceId1 = createIRI("http://example.com/1");
    private static final IRI testResourceId2 = createIRI("http://example.com/2");
    private static final IRI testResourceId3 = createIRI("http://example.com/3");

    private static IRI badId = createIRI("http://bad.com");

    private final ImmutableDataService<Resource> testImmutableService = new TestableImmutableService();

    private final MutableDataService<Resource> testMutableService = new TestableMutableDataService();

    private final ResourceService testable = new TestableJoiningResourceService(testImmutableService,
            testMutableService);

    private static IRI createIRI(final String value) {
        return TrellisUtils.getInstance().createIRI(value);
    }

    private static Quad createQuad(final BlankNodeOrIRI g, final BlankNodeOrIRI s, final IRI p, final RDFTerm o) {
        return TrellisUtils.getInstance().createQuad(g, s, p, o);
    }

    private static class TestableRetrievalService implements RetrievalService<Resource> {

        protected final Map<IRI, Resource> resources = synchronizedMap(new HashMap<>());

        @Override
        public CompletionStage<Resource> get(final IRI identifier) {
            return completedFuture(resources.getOrDefault(identifier, MISSING_RESOURCE));
        }

        protected CompletionStage<Void> isntBadId(final IRI identifier) {
            return runAsync(() -> {
                if (identifier.equals(badId)) {
                    throw new RuntimeTrellisException("Expected Exception");
                }
            });
        }

    }

    private static class TestableImmutableService extends TestableRetrievalService
            implements ImmutableDataService<Resource> {

        @Override
        public CompletionStage<Void> add(final IRI identifier, final Dataset dataset) {
            resources.compute(identifier, (id, old) -> {
                final TestResource newRes = new TestResource(id, dataset);
                return old == null ? newRes : new RetrievableResource(old, newRes);
            });
            return isntBadId(identifier);
        }
    }

    private static class TestableMutableDataService extends TestableRetrievalService
            implements MutableDataService<Resource> {

        @Override
        public CompletionStage<Void> create(final Metadata metadata, final Dataset dataset) {
            resources.put(metadata.getIdentifier(), new TestResource(metadata.getIdentifier(), dataset));
            return isntBadId(metadata.getIdentifier());
        }

        @Override
        public CompletionStage<Void> replace(final Metadata metadata, final Dataset dataset) {
            resources.replace(metadata.getIdentifier(), new TestResource(metadata.getIdentifier(), dataset));
            return isntBadId(metadata.getIdentifier());
        }

        @Override
        public CompletionStage<Void> delete(final Metadata metadata) {
            resources.remove(metadata.getIdentifier());
            return isntBadId(metadata.getIdentifier());
        }
    };

    private static class TestableJoiningResourceService extends JoiningResourceService {

        public TestableJoiningResourceService(final ImmutableDataService<Resource> immutableData,
                final MutableDataService<Resource> mutableData) {
            super(mutableData, immutableData);
        }

        @Override
        public String generateIdentifier() {
            return "new-identifier";
        }

        @Override
        public Set<IRI> supportedInteractionModels() {
            return emptySet();
        }

        @Override
        public CompletionStage<Void> touch(final IRI identifier) {
            return completedFuture(null);
        }
    }

    private static class TestResource implements Resource {

        private final Instant mod = now();
        private final Dataset dataset = TrellisUtils.getInstance().createDataset();
        private final IRI id;

        public TestResource(final IRI id, final Quad... quads) {
            this.id = id;
            for (final Quad q : quads) {
                dataset.add(q);
            }
        }

        public TestResource(final IRI id, final Dataset quads) {
            this.id = id;
            quads.stream().forEach(dataset::add);
        }

        @Override
        public IRI getIdentifier() {
            return id;
        }

        @Override
        public IRI getInteractionModel() {
            return LDP.RDFSource;
        }

        @Override
        public Optional<IRI> getContainer() {
            return empty();
        }

        @Override
        public Stream<Quad> stream() {
            return dataset.stream().map(Quad.class::cast);
        }

        @Override
        public Instant getModified() {
            return mod;
        }

        @Override
        public boolean hasAcl() {
            return false;
        }

    }

    @Test
    public void testRoundtripping() {
        final Quad testQuad = createQuad(testResourceId1, testResourceId1, testResourceId1, badId);
        final Resource testResource = new TestResource(testResourceId1, testQuad);
        assertNull(testable.create(Metadata.builder(testResourceId1).interactionModel(LDP.RDFSource).build(),
                testResource.dataset()).toCompletableFuture().join(), "Couldn't create a resource!");
        Resource retrieved = testable.get(testResourceId1).toCompletableFuture().join();
        assertEquals(testResource.getIdentifier(), retrieved.getIdentifier(),
                "Resource was retrieved with wrong ID!");
        assertEquals(testResource.stream().findFirst().get(), retrieved.stream().findFirst().get(),
                "Resource was retrieved with wrong data!");

        final Quad testQuad2 = createQuad(testResourceId1, badId, testResourceId1, badId);
        final Resource testResource2 = new TestResource(testResourceId1, testQuad2);
        assertNull(testable.replace(Metadata.builder(testResource2).interactionModel(LDP.RDFSource).build(),
                testResource2.dataset()).toCompletableFuture().join(), "Couldn't replace resource!");
        retrieved = testable.get(testResourceId1).toCompletableFuture().join();
        assertEquals(testResource2.getIdentifier(), retrieved.getIdentifier(),
                "Resource was retrieved with wrong ID!");
        assertEquals(testResource2.stream().findFirst().get(), retrieved.stream().findFirst().get(),
                "Resource was retrieved with wrong data!");

        assertNull(testable.delete(Metadata.builder(testResourceId1).interactionModel(LDP.RDFSource).build())
                .toCompletableFuture().join(), "Couldn't delete resource!");
        assertEquals(MISSING_RESOURCE, testable.get(testResourceId1).toCompletableFuture().join(),
                "Found resource after deleting it!");
    }

    @Test
    public void testMergingBehavior() {
        final Quad testMutableQuad = createQuad(testResourceId2, testResourceId2, testResourceId1, badId);
        final Quad testImmutableQuad = createQuad(testResourceId2, testResourceId2, testResourceId1, badId);

        // store some data in mutable and immutable sides under the same resource ID
        final Resource testMutableResource = new TestResource(testResourceId2, testMutableQuad);
        assertNull(testable.create(Metadata.builder(testMutableResource).build(), testMutableResource.dataset())
                .toCompletableFuture().join(), "Couldn't create a resource!");
        final Resource testImmutableResource = new TestResource(testResourceId2, testImmutableQuad);
        assertNull(testable.add(testResourceId2, testImmutableResource.dataset()).toCompletableFuture().join(),
                "Couldn't create an immutable resource!");

        final Resource retrieved = testable.get(testResourceId2).toCompletableFuture().join();
        assertEquals(testMutableResource.getIdentifier(), retrieved.getIdentifier(),
                "Resource was retrieved with wrong ID!");
        final Dataset quads = retrieved.dataset();
        assertTrue(quads.contains(testImmutableQuad), "Resource was retrieved without its immutable data!");
        assertTrue(quads.contains(testMutableQuad), "Resource was retrieved without its mutable data!");
        quads.remove(testImmutableQuad);
        quads.remove(testMutableQuad);
        assertEquals(0, quads.size(), "Resource was retrieved with too much data!");
    }

    @Test
    public void testBadPersist() {
        final Quad testQuad = createQuad(badId, testResourceId1, testResourceId1, badId);
        final Resource testResource = new TestResource(badId, testQuad);
        assertThrows(CompletionException.class,
                () -> testable.create(Metadata.builder(testResource).build(), testResource.dataset())
                        .toCompletableFuture().join(),
                "Could create a resource when underlying services should reject it!");
    }

    @Test
    public void testAppendSemantics() {
        final Quad testFirstQuad = createQuad(testResourceId3, testResourceId2, testResourceId1, badId);
        final Quad testSecondQuad = createQuad(testResourceId3, testResourceId2, testResourceId1, badId);

        // store some data in mutable and immutable sides under the same resource ID
        final Resource testFirstResource = new TestResource(testResourceId3, testFirstQuad);
        assertNull(testable.add(testResourceId3, testFirstResource.dataset()).toCompletableFuture().join(),
                "Couldn't create an immutable resource!");
        final Resource testSecondResource = new TestResource(testResourceId3, testSecondQuad);
        assertNull(testable.add(testResourceId3, testSecondResource.dataset()).toCompletableFuture().join(),
                "Couldn't add to an immutable resource!");

        final Resource retrieved = testable.get(testResourceId3).toCompletableFuture().join();
        assertEquals(testResourceId3, retrieved.getIdentifier(), "Resource was retrieved with wrong ID!");
        final Dataset quads = retrieved.dataset();
        assertTrue(quads.contains(testFirstQuad), "Resource was retrieved without its immutable data!");
        assertTrue(quads.contains(testSecondQuad), "Resource was retrieved without its mutable data!");
        quads.remove(testFirstQuad);
        quads.remove(testSecondQuad);
        assertEquals(0, quads.size(), "Resource was retrieved with too much data!");
    }

    @Test
    public void testRetrievableResource() {
        final Instant time = now();
        final Quad quad = createQuad(testResourceId2, testResourceId2, testResourceId1, badId);
        final Resource mockMutable = mock(Resource.class);

        when(mockMutable.getInteractionModel()).thenReturn(LDP.RDFSource);
        when(mockMutable.getModified()).thenReturn(time);
        when(mockMutable.hasAcl()).thenReturn(true);
        when(mockMutable.getContainer()).thenReturn(empty());
        when(mockMutable.stream()).thenAnswer(inv -> Stream.of(quad));

        final Resource res = new RetrievableResource(mockMutable, null);
        assertEquals(LDP.RDFSource, res.getInteractionModel(), "Resource retrieved with wrong interaction model!");
        assertEquals(time, res.getModified(), "Resource has wrong modified date!");
        assertTrue(res.hasAcl(), "Resource is missing ACL!");
        assertFalse(res.getContainer().isPresent(), "Unexpected parent resource!");
        assertTrue(res.stream().anyMatch(quad::equals), "Expected quad not present in resource stream!");
    }

    @Test
    public void testPersistableResource() {
        final Instant time = now();
        final IRI identifier = createIRI("trellis:identifier");
        final Quad quad = createQuad(testResourceId2, testResourceId2, testResourceId1, badId);
        final Dataset dataset = TrellisUtils.getInstance().createDataset();
        dataset.add(quad);

        final Resource res = new JoiningResourceService.PersistableResource(identifier, LDP.Container, null,
                dataset);
        assertEquals(identifier, res.getIdentifier(), "Resource has wrong ID!");
        assertEquals(LDP.Container, res.getInteractionModel(), "Resource has wrong LDP type!");
        assertFalse(res.getModified().isBefore(time), "Resource modification date predates its creation!");
        assertFalse(res.getModified().isAfter(now()), "Resource modification date is too late!");
        assertTrue(res.stream().anyMatch(quad::equals), "Expected quad not present in resource stream");
        assertFalse(res.getContainer().isPresent(), "Expected no parent container");
        assertThrows(UnsupportedOperationException.class, res::hasAcl, "ACL retrieval should throw an exception!");
    }
}