MARMOTTA-463: Initial implementation for HTTP PUT in LDP
Project: http://git-wip-us.apache.org/repos/asf/marmotta/repo Commit: http://git-wip-us.apache.org/repos/asf/marmotta/commit/4a188e64 Tree: http://git-wip-us.apache.org/repos/asf/marmotta/tree/4a188e64 Diff: http://git-wip-us.apache.org/repos/asf/marmotta/diff/4a188e64 Branch: refs/heads/ldp Commit: 4a188e64c1c806cd5d57c4acce96ae4a5943316e Parents: e25131f Author: Jakob Frank <[email protected]> Authored: Thu Jun 12 17:09:49 2014 +0200 Committer: Jakob Frank <[email protected]> Committed: Thu Jun 12 17:09:49 2014 +0200 ---------------------------------------------------------------------- .../marmotta/platform/ldp/api/LdpService.java | 9 +- .../IncompatibleResourceTypeException.java | 59 ++++++++++ .../InvalidInteractionModelException.java | 2 +- .../InvalidModificationException.java | 2 +- .../platform/ldp/exceptions/LDPException.java | 56 +++++++++ .../platform/ldp/services/LdpServiceImpl.java | 79 ++++++++++++- .../platform/ldp/util/EntityTagUtils.java | 19 +++ .../platform/ldp/webservices/LdpWebService.java | 33 ++++-- .../ldp/webservices/LdpWebServiceTest.java | 115 ++++++++++++++++++- .../ldp/webservices/util/HeaderMatchers.java | 13 ++- .../src/test/resources/test_update.ttl | 27 +++++ .../src/test/resources/test_update_invalid.ttl | 25 ++++ 12 files changed, 418 insertions(+), 21 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/api/LdpService.java ---------------------------------------------------------------------- diff --git a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/api/LdpService.java b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/api/LdpService.java index 12cbbee..5a15d12 100644 --- a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/api/LdpService.java +++ b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/api/LdpService.java @@ -18,6 +18,7 @@ package org.apache.marmotta.platform.ldp.api; import org.apache.marmotta.commons.vocabulary.LDP; +import org.apache.marmotta.platform.ldp.exceptions.IncompatibleResourceTypeException; import org.apache.marmotta.platform.ldp.exceptions.InvalidInteractionModelException; import org.apache.marmotta.platform.ldp.exceptions.InvalidModificationException; import org.apache.marmotta.platform.ldp.patch.InvalidPatchDocumentException; @@ -35,8 +36,7 @@ import javax.ws.rs.core.Link; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.Date; -import java.util.List; +import java.util.*; /** * LDP Service @@ -81,6 +81,8 @@ public interface LdpService { } + public static final Set<URI> SERVER_MANAGED_PROPERTIES = new HashSet<>(Arrays.asList(LDP.contains)); + boolean exists(RepositoryConnection connection, String resource) throws RepositoryException; boolean exists(RepositoryConnection connection, URI resource) throws RepositoryException; @@ -149,6 +151,9 @@ public interface LdpService { */ String addResource(RepositoryConnection connection, URI container, URI resource, InteractionModel interactionModel, String type, InputStream stream) throws RepositoryException, IOException, RDFParseException; + String updateResource(RepositoryConnection con, String resource, InputStream stream, String type) throws RepositoryException, IncompatibleResourceTypeException, RDFParseException, IOException, InvalidModificationException; + + String updateResource(RepositoryConnection con, URI resource, InputStream stream, String type) throws RepositoryException, IncompatibleResourceTypeException, IOException, RDFParseException, InvalidModificationException; List<Statement> getLdpTypes(RepositoryConnection connection, String resource) throws RepositoryException; http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/IncompatibleResourceTypeException.java ---------------------------------------------------------------------- diff --git a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/IncompatibleResourceTypeException.java b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/IncompatibleResourceTypeException.java new file mode 100644 index 0000000..c9270ac --- /dev/null +++ b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/IncompatibleResourceTypeException.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.marmotta.platform.ldp.exceptions; + +import org.apache.commons.lang3.StringUtils; + +/** + * Exception to be thrown if a RDFS-Resource is available where a Non-RDF-Resource is expected, and vice versa. + */ +public class IncompatibleResourceTypeException extends LDPException { + + public IncompatibleResourceTypeException(String expected, String available) { + super(createMessage(null, expected, available)); + } + + public IncompatibleResourceTypeException(String message, String expected, String available) { + super(createMessage(message, expected, available)); + } + + private static String createMessage(String message, String expected, String available) { + if (StringUtils.isBlank(message)) { + return String.format("Expected %s but %s was provided", expected, available); + } else { + return String.format("%s: expected %s but %s was provided", message, expected, available); + } + } +} http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidInteractionModelException.java ---------------------------------------------------------------------- diff --git a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidInteractionModelException.java b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidInteractionModelException.java index c083d48..7c87041 100644 --- a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidInteractionModelException.java +++ b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidInteractionModelException.java @@ -24,7 +24,7 @@ package org.apache.marmotta.platform.ldp.exceptions; * * @author Jakob Frank ([email protected]) */ -public class InvalidInteractionModelException extends Exception { +public class InvalidInteractionModelException extends LDPException { private final String href; http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidModificationException.java ---------------------------------------------------------------------- diff --git a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidModificationException.java b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidModificationException.java index e529ae8..f255fc9 100644 --- a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidModificationException.java +++ b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidModificationException.java @@ -24,7 +24,7 @@ package org.apache.marmotta.platform.ldp.exceptions; * * @see <a href="https://dvcs.w3.org/hg/ldpwg/raw-file/default/ldp.html#ldpr-put-servermanagedprops">LDP Spec, Sec. 5.5.2</a> */ -public class InvalidModificationException extends Exception { +public class InvalidModificationException extends LDPException { public InvalidModificationException() { super(); http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/LDPException.java ---------------------------------------------------------------------- diff --git a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/LDPException.java b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/LDPException.java new file mode 100644 index 0000000..a34e650 --- /dev/null +++ b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/LDPException.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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. + */ + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.marmotta.platform.ldp.exceptions; + +/** + * Created by jakob on 12.06.14. + */ +public class LDPException extends Exception { + public LDPException() { + super(); + } + + public LDPException(String message) { + super(message); + } + + public LDPException(String message, Throwable cause) { + super(message, cause); + } + + public LDPException(Throwable cause) { + super(cause); + } +} http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/services/LdpServiceImpl.java ---------------------------------------------------------------------- diff --git a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/services/LdpServiceImpl.java b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/services/LdpServiceImpl.java index 8d28aae..d4e4e64 100644 --- a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/services/LdpServiceImpl.java +++ b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/services/LdpServiceImpl.java @@ -26,6 +26,7 @@ import org.apache.marmotta.commons.vocabulary.LDP; import org.apache.marmotta.platform.core.api.config.ConfigurationService; import org.apache.marmotta.platform.ldp.api.LdpBinaryStoreService; import org.apache.marmotta.platform.ldp.api.LdpService; +import org.apache.marmotta.platform.ldp.exceptions.IncompatibleResourceTypeException; import org.apache.marmotta.platform.ldp.exceptions.InvalidInteractionModelException; import org.apache.marmotta.platform.ldp.exceptions.InvalidModificationException; import org.apache.marmotta.platform.ldp.patch.InvalidPatchDocumentException; @@ -40,11 +41,12 @@ import org.openrdf.model.vocabulary.RDF; import org.openrdf.repository.RepositoryConnection; import org.openrdf.repository.RepositoryException; import org.openrdf.repository.RepositoryResult; +import org.openrdf.repository.event.base.InterceptingRepositoryConnectionWrapper; +import org.openrdf.repository.event.base.RepositoryConnectionInterceptorAdapter; import org.openrdf.rio.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.PostConstruct; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import javax.ws.rs.core.EntityTag; @@ -53,7 +55,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Date; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * LDP Service default implementation @@ -319,6 +323,79 @@ public class LdpServiceImpl implements LdpService { } @Override + public String updateResource(RepositoryConnection con, String resource, InputStream stream, String type) throws RepositoryException, IncompatibleResourceTypeException, RDFParseException, IOException, InvalidModificationException { + return updateResource(con, buildURI(resource), stream, type); + } + + @Override + public String updateResource(final RepositoryConnection con, final URI resource, InputStream stream, String type) throws RepositoryException, IncompatibleResourceTypeException, IOException, RDFParseException, InvalidModificationException { + final ValueFactory valueFactory = con.getValueFactory(); + final Literal now = valueFactory.createLiteral(new Date()); + + con.remove(resource, DCTERMS.modified, null, ldpContext); + con.add(resource, DCTERMS.modified, now, ldpContext); + + final RDFFormat rdfFormat = Rio.getParserFormatForMIMEType(type); + // Check submitted format vs. real resource type (RDF-S vs. Non-RDF) + if (rdfFormat == null && isNonRdfSourceResource(con, resource)) { + log.debug("Updating <{}> as LDP-NR (binary) - {}", resource, type); + + final Literal format = valueFactory.createLiteral(type); + + con.remove(resource, DCTERMS.format, null, ldpContext); + con.add(resource, DCTERMS.format, format, ldpContext); //nie:mimeType ? + + final URI ldp_rs = getRdfSourceForNonRdfSource(con, resource); + if (ldp_rs != null) { + con.remove(ldp_rs, DCTERMS.modified, null, ldpContext); + con.add(ldp_rs, DCTERMS.modified, now, ldpContext); + log.trace("Updated Meta-Data of LDP-RS <{}> for LDP-NR <{}>; Modified: {}", ldp_rs, resource, now); + } else { + log.debug("LDP-RS for LDP-NR <{}> not found", resource); + } + log.trace("Meta-Data for <{}> updated; Format: {}, Modified: {}", resource, format, now); + + binaryStore.store(resource, stream);//TODO: exceptions control + + log.trace("LDP-NR <{}> updated", resource); + return resource.stringValue(); + } else if (rdfFormat != null && isRdfSourceResource(con, resource)) { + log.debug("Updating <{}> as LDP-RS - {}", resource, rdfFormat.getDefaultMIMEType()); + + con.clear(resource); + final InterceptingRepositoryConnectionWrapper filtered = new InterceptingRepositoryConnectionWrapper(con.getRepository(), con); + final Set<URI> deniedProperties = new HashSet<>(); + filtered.addRepositoryConnectionInterceptor(new RepositoryConnectionInterceptorAdapter() { + @Override + public boolean add(RepositoryConnection conn, Resource subject, URI predicate, Value object, Resource... contexts) { + if (resource.equals(subject) && SERVER_MANAGED_PROPERTIES.contains(predicate)) { + deniedProperties.add(predicate); + return true; + } + return false; + } + }); + + filtered.add(stream, resource.stringValue(), rdfFormat, resource); + + if (!deniedProperties.isEmpty()) { + final URI prop = deniedProperties.iterator().next(); + log.debug("Invalid property modification in update: <{}> is a server controlled property", prop); + throw new InvalidModificationException(String.format("Must not update <%s> using PUT", prop)); + } + log.trace("LDP-RS <{}> updated", resource); + return resource.stringValue(); + } else if (rdfFormat == null) { + final String mimeType = getMimeType(con, resource); + log.debug("Incompatible replacement: Can't replace {} with {}", mimeType, type); + throw new IncompatibleResourceTypeException(mimeType, type); + } else { + log.debug("Incompatible replacement: Can't replace a LDP-RS with {}", type); + throw new IncompatibleResourceTypeException("RDF", type); + } + } + + @Override public EntityTag generateETag(RepositoryConnection connection, String resource) throws RepositoryException { return generateETag(connection, buildURI(resource)); } http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/EntityTagUtils.java ---------------------------------------------------------------------- diff --git a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/EntityTagUtils.java b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/EntityTagUtils.java index bc58a60..a695096 100644 --- a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/EntityTagUtils.java +++ b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/EntityTagUtils.java @@ -50,4 +50,23 @@ public class EntityTagUtils { } } + /** + * This is a workaround for <a href="https://issues.jboss.org/browse/RESTEASY-1019">RESTEASY-1019</a> + * + * @see javax.ws.rs.core.EntityTag#equals(Object) + * @deprecated use {@link javax.ws.rs.core.EntityTag#valueOf(String)} when RESTEASY-1019 is fixed. + */ + @Deprecated + public static EntityTag parseEntityTag(String headerValue) { + boolean weak = false; + if (headerValue.startsWith("W/")) { + weak = true; + headerValue = headerValue.substring(2); + } + if (headerValue.startsWith("\"") && headerValue.endsWith("\"")) { + headerValue = headerValue.substring(1, headerValue.length() -1); + } + return new EntityTag(headerValue,weak); + } + } http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/webservices/LdpWebService.java ---------------------------------------------------------------------- diff --git a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/webservices/LdpWebService.java b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/webservices/LdpWebService.java index 81dedf1..0a24fcf 100644 --- a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/webservices/LdpWebService.java +++ b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/webservices/LdpWebService.java @@ -22,6 +22,7 @@ import org.apache.marmotta.commons.vocabulary.LDP; import org.apache.marmotta.platform.core.api.config.ConfigurationService; import org.apache.marmotta.platform.core.api.triplestore.SesameService; import org.apache.marmotta.platform.ldp.api.LdpService; +import org.apache.marmotta.platform.ldp.exceptions.IncompatibleResourceTypeException; import org.apache.marmotta.platform.ldp.exceptions.InvalidInteractionModelException; import org.apache.marmotta.platform.ldp.exceptions.InvalidModificationException; import org.apache.marmotta.platform.ldp.patch.InvalidPatchDocumentException; @@ -47,6 +48,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; +import java.util.Set; import java.util.UUID; /** @@ -302,6 +304,7 @@ public class LdpWebService { con.begin(); if (!ldpService.exists(con, resource)) { + log.trace("Resource does not exists: {}", resource); final Response.ResponseBuilder resp = createResponse(con, Response.Status.NOT_FOUND, resource); con.rollback(); return resp.build(); @@ -325,14 +328,28 @@ public class LdpWebService { } } - /* - * TODO: PUT implementation - * - * clients should not be allowed to update LDPC-membership triples -> 409 Conflict (Sec. 4.2.4.3) - * - * if the target resource exists, replace ALL data of the target. - */ - final Response.ResponseBuilder resp = createResponse(con, Response.Status.NOT_IMPLEMENTED, resource); + final String mimeType = LdpUtils.getMimeType(type); + log.trace("updating resource <{}>", resource); + // NOTE: newResource == resource for now, this might change in the future. + final String newResource = ldpService.updateResource(con, resource, postBody, mimeType); + + final Response.ResponseBuilder resp; + if (resource.equals(newResource)) { + log.trace("PUT update for <{}> successful", resource); + resp = createResponse(con, Response.Status.OK, resource); + } else { + log.trace("PUT on <{}> created new resource <{}>", resource, newResource); + resp = createResponse(con, Response.Status.CREATED, resource).location(java.net.URI.create(newResource)); + } + con.commit(); + + return resp.build(); + } catch (IOException | RDFParseException e) { + final Response.ResponseBuilder resp = createResponse(con, Response.Status.BAD_REQUEST, resource).entity(e.getClass().getSimpleName() + ": " + e.getMessage()); + con.rollback(); + return resp.build(); + } catch (InvalidModificationException | IncompatibleResourceTypeException e) { + final Response.ResponseBuilder resp = createResponse(con, Response.Status.CONFLICT, resource).entity(e.getClass().getSimpleName() + ": " + e.getMessage()); con.rollback(); return resp.build(); } catch (final Throwable t) { http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/LdpWebServiceTest.java ---------------------------------------------------------------------- diff --git a/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/LdpWebServiceTest.java b/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/LdpWebServiceTest.java index 8cd5c69..214926e 100644 --- a/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/LdpWebServiceTest.java +++ b/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/LdpWebServiceTest.java @@ -20,23 +20,28 @@ package org.apache.marmotta.platform.ldp.webservices; import com.jayway.restassured.RestAssured; import org.apache.commons.io.IOUtils; import org.apache.marmotta.commons.sesame.test.SesameMatchers; +import org.apache.marmotta.commons.sesame.test.base.SesameMatcher; import org.apache.marmotta.commons.util.HashUtils; import org.apache.marmotta.commons.vocabulary.LDP; import org.apache.marmotta.platform.core.exception.io.MarmottaImportException; import org.apache.marmotta.platform.core.test.base.JettyMarmotta; +import org.apache.marmotta.platform.ldp.util.EntityTagUtils; import org.apache.marmotta.platform.ldp.webservices.util.HeaderMatchers; import org.hamcrest.CoreMatchers; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; +import org.openrdf.model.URI; import org.openrdf.model.impl.LiteralImpl; import org.openrdf.model.impl.URIImpl; import org.openrdf.model.vocabulary.DCTERMS; import org.openrdf.model.vocabulary.RDF; +import org.openrdf.model.vocabulary.RDFS; import org.openrdf.rio.RDFFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.ws.rs.core.EntityTag; import javax.ws.rs.core.Link; import javax.ws.rs.core.UriBuilder; import java.io.IOException; @@ -234,9 +239,9 @@ public class LdpWebServiceTest { // now the resource hasType RestAssured - .given() + .given() .header("Accept", RDFFormat.TURTLE.getDefaultMIMEType()) - .expect() + .expect() .statusCode(200) .header("Link", CoreMatchers.anyOf( //TODO: RestAssured only checks the FIRST header... HeaderMatchers.isLink(LdpWebService.LDP_SERVER_CONSTRAINTS, "describedby"), @@ -252,7 +257,7 @@ public class LdpWebServiceTest { SesameMatchers.hasStatement(new URIImpl(baseUrl + newResource+".png"), DCTERMS.FORMAT, new LiteralImpl(mimeType)), SesameMatchers.hasStatement(new URIImpl(baseUrl + newResource+".png"), DCTERMS.IS_FORMAT_OF, new URIImpl(baseUrl + newResource)) )) - .get(newResource + ".png"); + .get(newResource + ".png"); // now check that the data is really there final String expectedMD5 = HashUtils.md5sum(LdpWebServiceTest.class.getResourceAsStream("/test.png")); @@ -309,6 +314,110 @@ public class LdpWebServiceTest { .post(container); } + @Test + public void testPUT() throws Exception { + final String container = baseUrl+LdpWebService.PATH + "/test"; + + final String put_valid = IOUtils.toString(LdpWebServiceTest.class.getResourceAsStream("/test_update.ttl"), "utf8"); + final String put_invalid = IOUtils.toString(LdpWebServiceTest.class.getResourceAsStream("/test_update_invalid.ttl"), "utf8"); + + + // Create a resource + final String resource = RestAssured + .given() + .header("Slug", "PUT") + .contentType(RDFFormat.TURTLE.getDefaultMIMEType()) + .body(testResourceTTL.getBytes()) + .expect() + .statusCode(201) + .post(container) + .getHeader("Location"); + final URI uri = new URIImpl(resource); + + // Check the data is there + EntityTag etag = EntityTagUtils.parseEntityTag(RestAssured + .given() + .header("Accept", RDFFormat.RDFXML.getDefaultMIMEType()) + .expect() + .contentType(RDFFormat.RDFXML.getDefaultMIMEType()) + .body(SesameMatchers.rdfStringMatches(RDFFormat.RDFXML, resource, + SesameMatchers.hasStatement(uri, RDF.TYPE, new URIImpl("http://example.com/Example")), + CoreMatchers.not(SesameMatchers.hasStatement(uri, RDFS.LABEL, null)), + CoreMatchers.not(SesameMatchers.hasStatement(uri, LDP.contains, uri)) + )) + .get(resource) + .getHeader("ETag")); + log.debug("ETag for <{}>: {}", resource, etag); + + // Try a Put without if-match header + RestAssured + .given() + .contentType(RDFFormat.TURTLE.getDefaultMIMEType()) + .body(put_valid.getBytes()) + .expect() + .statusCode(428) + .put(resource); + + // Try a Put with wrong if-match header + RestAssured + .given() + .header("If-Match", new EntityTag("invalid").toString()) + .contentType(RDFFormat.TURTLE.getDefaultMIMEType()) + .body(put_valid.getBytes()) + .expect() + .statusCode(412) + .put(resource); + + // Try a Put + RestAssured + .given() + .header("If-Match", etag.toString()) + .contentType(RDFFormat.TURTLE.getDefaultMIMEType()) + .body(put_valid.getBytes()) + .expect() + .statusCode(200) + .put(resource); + + // Check the new data is there + etag = EntityTagUtils.parseEntityTag(RestAssured + .given() + .header("Accept", RDFFormat.RDFXML.getDefaultMIMEType()) + .expect() + .contentType(RDFFormat.RDFXML.getDefaultMIMEType()) + .body(SesameMatchers.rdfStringMatches(RDFFormat.RDFXML, resource, + SesameMatchers.hasStatement(uri, RDF.TYPE, new URIImpl("http://example.com/Example")), + SesameMatchers.hasStatement(uri, RDFS.LABEL, null), + CoreMatchers.not(SesameMatchers.hasStatement(uri, LDP.contains, uri)) + )) + .get(resource) + .header("ETag")); + + // Try an invalid PUT (server-controlled property) + // Try a Put + RestAssured + .given() + .header("If-Match", etag.toString()) + .contentType(RDFFormat.TURTLE.getDefaultMIMEType()) + .body(put_invalid.getBytes()) + .expect() + .statusCode(409) + .put(resource); + + // Check the data is still there + RestAssured + .given() + .header("Accept", RDFFormat.RDFXML.getDefaultMIMEType()) + .expect() + .contentType(RDFFormat.RDFXML.getDefaultMIMEType()) + .header("ETag", HeaderMatchers.hasEntityTag(etag)) + .body(SesameMatchers.rdfStringMatches(RDFFormat.RDFXML, resource, + SesameMatchers.hasStatement(uri, RDF.TYPE, new URIImpl("http://example.com/Example")), + SesameMatchers.hasStatement(uri, RDFS.LABEL, null), + CoreMatchers.not(SesameMatchers.hasStatement(uri, LDP.contains, uri)) + )) + .get(resource); + } + @AfterClass public static void tearDown() { marmotta.shutdown(); http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/util/HeaderMatchers.java ---------------------------------------------------------------------- diff --git a/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/util/HeaderMatchers.java b/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/util/HeaderMatchers.java index a109c3d..80f4026 100644 --- a/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/util/HeaderMatchers.java +++ b/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/util/HeaderMatchers.java @@ -79,7 +79,14 @@ public class HeaderMatchers { } public static Matcher<String> hasEntityTag(String value, boolean weakTag) { - final EntityTag expected = new EntityTag(value, weakTag); + return hasEntityTag(new EntityTag(value, weakTag)); + } + + public static Matcher<String> hasEntityTag(String value) { + return hasEntityTag(value, false); + } + + public static Matcher<String> hasEntityTag(final EntityTag expected) { return new CustomTypeSafeMatcher<String>(String.format("an EntityTag %s", expected)) { @Override protected boolean matchesSafely(String item) { @@ -87,8 +94,4 @@ public class HeaderMatchers { } }; } - - public static Matcher<String> hasEntityTag(String value) { - return hasEntityTag(value, false); - } } http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/test/resources/test_update.ttl ---------------------------------------------------------------------- diff --git a/platform/marmotta-ldp/src/test/resources/test_update.ttl b/platform/marmotta-ldp/src/test/resources/test_update.ttl new file mode 100644 index 0000000..29749f5 --- /dev/null +++ b/platform/marmotta-ldp/src/test/resources/test_update.ttl @@ -0,0 +1,27 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF 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. + +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>. +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>. +@prefix dct: <http://purl.org/dc/terms/>. +@prefix skos: <http://www.w3.org/2004/02/skos/core#>. +@prefix ldp: <http://www.w3.org/ns/ldp#>. + +<> a <http://example.com/Example> ; + dct:title "Example"@en , "Beispiel"@de ; + dct:description "Just an example"@en, "Einfach ein Beispiel"@de . + +<> rdfs:label "LDP PUT Test". http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/test/resources/test_update_invalid.ttl ---------------------------------------------------------------------- diff --git a/platform/marmotta-ldp/src/test/resources/test_update_invalid.ttl b/platform/marmotta-ldp/src/test/resources/test_update_invalid.ttl new file mode 100644 index 0000000..bbb4baa --- /dev/null +++ b/platform/marmotta-ldp/src/test/resources/test_update_invalid.ttl @@ -0,0 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF 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. + +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>. +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>. +@prefix dct: <http://purl.org/dc/terms/>. +@prefix skos: <http://www.w3.org/2004/02/skos/core#>. +@prefix ldp: <http://www.w3.org/ns/ldp#>. + +<> a <http://example.com/Example> ; + dct:title "Example"@en , "Beispiel"@de ; + ldp:contains <> .
