Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,13 @@ subprojects {
jaxbVersion = '2.3.0'

/* Transitive dependencies (for Karaf provisioning) */
guavaVersion = '20.0' // via mustache 0.9.5
transitiveGuavaVersion = '20.0' // via mustache 0.9.5

/* Testing */
apiguardianVersion = '1.0.0'
awaitilityVersion = '3.1.0'
commonsTextVersion = '1.3'
guavaVersion = '24.1-jre'
jerseyVersion = '2.25.1'
jsonVersion = '1.1.2'
junitVersion = '5.1.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.stream.Stream;

Expand Down Expand Up @@ -235,6 +236,13 @@ default Stream<? extends Quad> export(final Collection<IRI> graphNames) {
getInstance().createQuad(resource.getIdentifier(), q.getSubject(), q.getPredicate(), q.getObject())));
}

/**
* Return a collection of interaction models supported by this Resource Service.
*
* @return a set of supported interaction models
*/
Set<IRI> supportedInteractionModels();

/**
* An identifier generator.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
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.concurrent.CompletableFuture.completedFuture;
import static org.junit.jupiter.api.Assertions.assertEquals;
Expand All @@ -30,6 +31,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
Expand Down Expand Up @@ -149,6 +151,10 @@ public String generateIdentifier() {
return "new-identifier";
}

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

private final ResourceService testable = new TestableJoiningResourceService(testImmutableService,
Expand Down
1 change: 1 addition & 0 deletions trellis-http/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dependencies {

testImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: jacksonVersion
testImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: jacksonVersion
testImplementation group: 'com.google.guava', name: 'guava', version: guavaVersion
testImplementation group: 'org.apache.commons', name: 'commons-text', version: commonsTextVersion
testImplementation group: 'org.apache.commons', name: 'commons-rdf-simple', version: commonsRdfVersion
testImplementation group: 'org.apache.tamaya', name: 'tamaya-core', version: tamayaVersion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,47 @@
import static java.util.Date.from;
import static java.util.Objects.nonNull;
import static java.util.Optional.ofNullable;
import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import static javax.ws.rs.core.Response.Status.GONE;
import static javax.ws.rs.core.Response.status;
import static org.apache.commons.rdf.api.RDFSyntax.JSONLD;
import static org.apache.commons.rdf.api.RDFSyntax.NTRIPLES;
import static org.apache.commons.rdf.api.RDFSyntax.TURTLE;
import static org.slf4j.LoggerFactory.getLogger;
import static org.trellisldp.api.RDFUtils.getInstance;

import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;

import javax.ws.rs.BadRequestException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response.ResponseBuilder;

import org.apache.commons.rdf.api.IRI;
import org.apache.commons.rdf.api.RDF;
import org.apache.commons.rdf.api.RDFSyntax;
import org.slf4j.Logger;
import org.trellisldp.api.AuditService;
import org.trellisldp.api.ConstraintService;
import org.trellisldp.api.Resource;
import org.trellisldp.api.ResourceService;
import org.trellisldp.http.domain.LdpRequest;
import org.trellisldp.vocabulary.LDP;
import org.trellisldp.vocabulary.Trellis;

/**
* @author acoburn
*/
public class BaseLdpHandler {

private static final Logger LOGGER = getLogger(BaseLdpHandler.class);

protected static final RDF rdf = getInstance();

protected AuditService audit;
Expand Down Expand Up @@ -122,4 +132,22 @@ protected static void checkCache(final Request request, final Instant modified,
throw new WebApplicationException(builder.build());
}
}

/**
* Check that the given interaction model is supported by the
* underlying persistence layer.
*
* @param interactionModel the interaction model
* @throws BadRequestException if the interaction model is not supported
*/
protected void checkInteractionModel(final IRI interactionModel) {
if (!resourceService.supportedInteractionModels().contains(interactionModel)) {
LOGGER.error("Interaction model not supported: ", interactionModel);
throw new BadRequestException("Unsupported interaction model provided: " + interactionModel,
status(BAD_REQUEST)
.link(Trellis.UnsupportedInteractionModel.getIRIString(), LDP.constrainedBy.getIRIString())
.entity("Unsupported interaction model provided").type(TEXT_PLAIN_TYPE).build());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ public ResponseBuilder deleteResource(final Resource res) {
final EntityTag etag = new EntityTag(buildEtagHash(identifier, res.getModified()));
checkCache(req.getRequest(), res.getModified(), etag);

// Check that the persistence layer supports LDP-R
checkInteractionModel(LDP.Resource);

LOGGER.debug("Deleting {}", identifier);

try (final TrellisDataset dataset = TrellisDataset.createDataset()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ public ResponseBuilder updateResource(final Resource res) {
final EntityTag etag = new EntityTag(buildEtagHash(identifier, res.getModified()));
checkCache(req.getRequest(), res.getModified(), etag);

// Check that the persistence layer supports LDP-RS
checkInteractionModel(LDP.RDFSource);

LOGGER.debug("Updating {} via PATCH", identifier);

final IRI graphName = ACL.equals(req.getExt()) ? PreferAccessControl : PreferUserManaged;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ public ResponseBuilder createResource() {
.filter(l -> l.startsWith(LDP.URI)).map(rdf::createIRI)
.filter(l -> !LDP.Resource.equals(l)).orElse(defaultType);

// Verify that the persistence layer supports the specified IXN model
checkInteractionModel(ldpType);

if (ldpType.equals(LDP.NonRDFSource) && rdfSyntax.isPresent()) {
throw new BadRequestException("Cannot save a NonRDFSource with RDF syntax");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ public ResponseBuilder setResource(final Resource res) {
.filter(l -> l.startsWith(LDP.URI)).map(rdf::createIRI).filter(l -> !LDP.Resource.equals(l))
.orElse(defaultType);

// Verify that the persistence layer supports the given interaction model
checkInteractionModel(ldpType);

LOGGER.debug("Using LDP Type: {}", ldpType);
// It is not possible to change the LDP type to a type that is not a subclass
checkInteractionModelChange(res, ldpType, isBinaryDescription);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*/
package org.trellisldp.http;

import static com.google.common.collect.Sets.newHashSet;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.time.Instant.MAX;
import static java.time.Instant.ofEpochSecond;
Expand Down Expand Up @@ -190,17 +191,12 @@ abstract class AbstractLdpResourceTest extends JerseyTest {
private static final IRI childIdentifier = rdf.createIRI(TRELLIS_DATA_PREFIX + CHILD_PATH);
private static final IRI deletedIdentifier = rdf.createIRI(TRELLIS_DATA_PREFIX + DELETED_PATH);
private static final IRI userDeletedIdentifier = rdf.createIRI(TRELLIS_DATA_PREFIX + USER_DELETED_PATH);
private static final Set<IRI> allInteractionModels = newHashSet(LDP.Resource, LDP.RDFSource, LDP.NonRDFSource,
LDP.Container, LDP.BasicContainer, LDP.DirectContainer, LDP.IndirectContainer);

protected static final String BASE_URL = "http://example.org/";

protected static final Set<IRI> allModes = new HashSet<>();

static {
allModes.add(ACL.Append);
allModes.add(ACL.Control);
allModes.add(ACL.Read);
allModes.add(ACL.Write);
}
protected static final Set<IRI> allModes = newHashSet(ACL.Append, ACL.Control, ACL.Read, ACL.Write);

@Mock
protected ResourceService mockResourceService;
Expand Down Expand Up @@ -255,6 +251,7 @@ public void setUpMocks() {
whenResource(mockResourceService.get(eq(root))).thenReturn(of(mockResource));
when(mockResourceService.get(eq(childIdentifier))).thenReturn(empty());
when(mockResourceService.get(eq(childIdentifier), any(Instant.class))).thenReturn(empty());
when(mockResourceService.supportedInteractionModels()).thenReturn(allInteractionModels);
whenResource(mockResourceService.get(eq(binaryIdentifier))).thenReturn(of(mockBinaryResource));
whenResource(mockResourceService.get(eq(binaryIdentifier), any(Instant.class)))
.thenReturn(of(mockBinaryVersionedResource));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@

import static java.time.Instant.ofEpochSecond;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static java.util.Date.from;
import static java.util.Optional.empty;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static javax.ws.rs.core.Link.fromUri;
import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE;
import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
import static javax.ws.rs.core.Response.Status.NO_CONTENT;
import static javax.ws.rs.core.Response.Status.PRECONDITION_FAILED;
import static javax.ws.rs.core.Response.status;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -106,6 +110,7 @@ public void setUp() {
final IRI iri = rdf.createIRI("trellis:repo");
when(mockResource.getModified()).thenReturn(time);
when(mockResource.getIdentifier()).thenReturn(iri);
when(mockResourceService.supportedInteractionModels()).thenReturn(singleton(LDP.Resource));
when(mockResourceService.getMementos(any())).thenReturn(emptyList());
when(mockResource.getExtraLinkRelations()).thenAnswer(inv -> Stream.empty());

Expand Down Expand Up @@ -184,6 +189,18 @@ public void testDeleteException() throws Exception {
assertEquals(INTERNAL_SERVER_ERROR, res.getStatusInfo());
}

@Test
public void testDeletePersistenceSupport() {
when(mockResourceService.supportedInteractionModels()).thenReturn(emptySet());
final DeleteHandler handler = new DeleteHandler(mockLdpRequest, mockResourceService, mockAuditService, baseUrl);

final BadRequestException ex = assertThrows(BadRequestException.class, () ->
handler.deleteResource(mockResource));
assertTrue(ex.getResponse().getLinks().stream().anyMatch(link ->
link.getUri().toString().equals(Trellis.UnsupportedInteractionModel.getIRIString()) &&
link.getRel().equals(LDP.constrainedBy.getIRIString())));
assertEquals(TEXT_PLAIN_TYPE, ex.getResponse().getMediaType());
}

@Test
public void testDeleteACLError() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@
package org.trellisldp.http.impl;

import static java.time.Instant.ofEpochSecond;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.stream.Stream.of;
import static javax.ws.rs.core.Link.fromUri;
import static javax.ws.rs.core.MediaType.TEXT_HTML_TYPE;
import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE;
import static javax.ws.rs.core.Response.Status.CONFLICT;
import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
import static javax.ws.rs.core.Response.Status.NO_CONTENT;
Expand Down Expand Up @@ -128,6 +131,7 @@ public void setUp() {
when(mockResource.getModified()).thenReturn(time);
when(mockResource.getInteractionModel()).thenReturn(LDP.RDFSource);
when(mockResource.getIdentifier()).thenReturn(identifier);
when(mockResourceService.supportedInteractionModels()).thenReturn(singleton(LDP.RDFSource));
when(mockResourceService.add(any(IRI.class), any(Session.class), any(Dataset.class)))
.thenReturn(completedFuture(true));
when(mockResourceService.replace(any(IRI.class), any(Session.class), any(IRI.class), any(Dataset.class)))
Expand Down Expand Up @@ -281,6 +285,21 @@ public void testError() {
assertThrows(BadRequestException.class, () -> patchHandler.updateResource(mockResource));
}

@Test
public void testNoLdpRsSupport() {
when(mockResourceService.supportedInteractionModels()).thenReturn(emptySet());

final PatchHandler patchHandler = new PatchHandler(mockLdpRequest, insert, mockAuditService,
mockResourceService, mockIoService, null);

final BadRequestException ex = assertThrows(BadRequestException.class, () ->
patchHandler.updateResource(mockResource));
assertTrue(ex.getResponse().getLinks().stream().anyMatch(link ->
link.getUri().toString().equals(Trellis.UnsupportedInteractionModel.getIRIString()) &&
link.getRel().equals(LDP.constrainedBy.getIRIString())));
assertEquals(TEXT_PLAIN_TYPE, ex.getResponse().getMediaType());
}

@Test
public void testException() throws Exception {
when(mockFuture.get()).thenThrow(new InterruptedException("Expected"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
*/
package org.trellisldp.http.impl;

import static com.google.common.collect.Sets.newHashSet;
import static java.net.URI.create;
import static java.time.Instant.ofEpochSecond;
import static java.util.Collections.emptySet;
import static java.util.UUID.randomUUID;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE;
Expand Down Expand Up @@ -44,6 +46,7 @@
import java.io.InputStream;
import java.time.Instant;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.function.Predicate;
import java.util.stream.Stream;
Expand Down Expand Up @@ -78,6 +81,7 @@
import org.trellisldp.http.domain.LdpRequest;
import org.trellisldp.vocabulary.DC;
import org.trellisldp.vocabulary.LDP;
import org.trellisldp.vocabulary.Trellis;

/**
* @author acoburn
Expand All @@ -88,6 +92,9 @@ public class PostHandlerTest {
private static final Instant time = ofEpochSecond(1496262729);
private static final String baseUrl = "http://example.org/repo/";
private static final RDF rdf = getInstance();
private static final Set<IRI> allInteractionModels = newHashSet(LDP.Resource, LDP.RDFSource,
LDP.NonRDFSource, LDP.Container, LDP.BasicContainer, LDP.DirectContainer, LDP.IndirectContainer);

private File entity;

@Mock
Expand Down Expand Up @@ -120,6 +127,7 @@ public class PostHandlerTest {
public void setUp() {
initMocks(this);
when(mockBinaryService.generateIdentifier()).thenReturn("file:" + randomUUID());
when(mockResourceService.supportedInteractionModels()).thenReturn(allInteractionModels);
when(mockResourceService.add(any(IRI.class), any(Session.class), any(Dataset.class)))
.thenReturn(completedFuture(true));
when(mockResourceService.create(any(IRI.class), any(Session.class), any(IRI.class), any(Dataset.class)))
Expand Down Expand Up @@ -261,6 +269,21 @@ public void testDefaultType5() throws IOException {
assertEquals(create(baseUrl + "newresource"), res.getLocation());
}

@Test
public void testUnsupportedType() {
when(mockResourceService.supportedInteractionModels()).thenReturn(emptySet());
when(mockRequest.getLink()).thenReturn(fromUri(LDP.Container.getIRIString()).rel("type").build());

final File entity = new File(getClass().getResource("/emptyData.txt").getFile());
final PostHandler postHandler = new PostHandler(mockRequest, "newresource", entity, mockResourceService,
mockIoService, mockBinaryService, null, mockAuditService);

final BadRequestException ex = assertThrows(BadRequestException.class, postHandler::createResource);
assertTrue(ex.getResponse().getLinks().stream().anyMatch(link ->
link.getUri().toString().equals(Trellis.UnsupportedInteractionModel.getIRIString()) &&
link.getRel().equals(LDP.constrainedBy.getIRIString())));
}

@Test
public void testEntity() throws IOException {
final String path = "newresource";
Expand Down
Loading