001/* 002 * Licensed to DuraSpace under one or more contributor license agreements. 003 * See the NOTICE file distributed with this work for additional information 004 * regarding copyright ownership. 005 * 006 * DuraSpace licenses this file to you under the Apache License, 007 * Version 2.0 (the "License"); you may not use this file except in 008 * compliance with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.fcrepo.integration.rdf; 019 020import static org.apache.jena.rdf.model.ResourceFactory.createProperty; 021import static org.fcrepo.kernel.api.RdfLexicon.CONTAINS; 022import static org.fcrepo.kernel.api.RdfLexicon.HAS_MEMBER_RELATION; 023import static org.fcrepo.kernel.api.RdfLexicon.PREMIS_NAMESPACE; 024import static org.fcrepo.kernel.api.RdfLexicon.RDF_NAMESPACE; 025import static org.junit.Assert.assertEquals; 026import static org.junit.Assert.assertFalse; 027import static org.junit.Assert.assertTrue; 028import static org.fcrepo.kernel.api.RdfLexicon.LDP_NAMESPACE; 029import static java.util.Arrays.asList; 030import static javax.ws.rs.core.HttpHeaders.CONTENT_DISPOSITION; 031import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE; 032import static javax.ws.rs.core.HttpHeaders.LINK; 033import static javax.ws.rs.core.MediaType.TEXT_PLAIN; 034import static javax.ws.rs.core.Response.Status.CREATED; 035import static org.fcrepo.kernel.api.FedoraTypes.FCR_METADATA; 036import static org.fcrepo.kernel.api.RdfLexicon.WRITABLE; 037import static org.fcrepo.kernel.api.RdfLexicon.REPOSITORY_NAMESPACE; 038import static org.fcrepo.kernel.api.RdfLexicon.MEMENTO_NAMESPACE; 039import static org.fcrepo.kernel.api.RdfLexicon.MEMENTO_TYPE; 040import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_CONTAINER; 041import static org.fcrepo.kernel.api.RdfLexicon.RESOURCE; 042import static org.fcrepo.kernel.api.RdfLexicon.HAS_SIZE; 043import static org.fcrepo.kernel.api.RdfLexicon.HAS_MIME_TYPE; 044import static org.fcrepo.kernel.api.RdfLexicon.HAS_ORIGINAL_NAME; 045import static org.apache.jena.rdf.model.ResourceFactory.createResource; 046 047import java.util.List; 048 049import org.apache.http.client.methods.CloseableHttpResponse; 050import org.apache.http.client.methods.HttpPatch; 051import org.apache.http.client.methods.HttpPost; 052import org.apache.http.entity.StringEntity; 053import org.apache.jena.datatypes.xsd.XSDDatatype; 054import org.apache.jena.rdf.model.Literal; 055import org.apache.jena.rdf.model.Model; 056import org.apache.jena.rdf.model.Property; 057import org.apache.jena.rdf.model.RDFNode; 058import org.apache.jena.rdf.model.Resource; 059import org.fcrepo.integration.http.api.AbstractResourceIT; 060import org.junit.Ignore; 061import org.junit.Test; 062 063/** 064 * @author bbpennel 065 */ 066@Ignore // TODO FIX THESE TESTS 067public class ServerManagedTriplesIT extends AbstractResourceIT { 068 069 // BINARY DESCRIPTIONS 070 public static final Property DESCRIBED_BY = 071 createProperty("http://www.iana.org/assignments/relation/describedby"); 072 073 private final static String NON_EXISTENT_PREDICATE = "any_predicate_will_do"; 074 075 private final static String NON_EXISTENT_TYPE = "any_type_is_fine"; 076 077 private final static List<String> INDIVIDUAL_SM_PREDS = asList( 078 PREMIS_NAMESPACE + "hasMessageDigest", 079 PREMIS_NAMESPACE + "hasFixity"); 080 081 @Test 082 public void testServerManagedPredicates() throws Exception { 083 for (final String predicate : INDIVIDUAL_SM_PREDS) { 084 verifyRejectLiteral(predicate); 085 verifyRejectUpdateLiteral(predicate); 086 } 087 } 088 089 @Test 090 public void testLdpNamespace() throws Exception { 091 // Verify that ldp:contains referencing another object is rejected 092 final String refPid = getRandomUniqueId(); 093 final String refURI = serverAddress + refPid; 094 createObject(refPid); 095 096 verifyRejectUriRef(CONTAINS.getURI(), refURI); 097 verifyRejectUpdateUriRef(CONTAINS.getURI(), refURI); 098 099 // Verify that ldp:hasMemberRelation referencing an SMT is rejected 100 verifyRejectUriRef(HAS_MEMBER_RELATION.getURI(), REPOSITORY_NAMESPACE + NON_EXISTENT_PREDICATE); 101 verifyRejectUpdateUriRef(HAS_MEMBER_RELATION.getURI(), REPOSITORY_NAMESPACE + NON_EXISTENT_PREDICATE); 102 verifyRejectUriRef(HAS_MEMBER_RELATION.getURI(), CONTAINS.getURI()); 103 verifyRejectUpdateUriRef(HAS_MEMBER_RELATION.getURI(), CONTAINS.getURI()); 104 105 // Verify that types in the ldp namespace are rejected 106 verifyRejectRdfType(RESOURCE.getURI()); 107 verifyRejectUpdateRdfType(RESOURCE.getURI()); 108 verifyRejectRdfType(LDP_NAMESPACE + NON_EXISTENT_TYPE); 109 verifyRejectUpdateRdfType(LDP_NAMESPACE + NON_EXISTENT_TYPE); 110 } 111 112 @Test 113 public void testFedoraNamespace() throws Exception { 114 // Verify rejection of known property 115 verifyRejectLiteral(WRITABLE.getURI()); 116 verifyRejectUpdateLiteral(WRITABLE.getURI()); 117 // Verify rejection of non-existent property 118 verifyRejectLiteral(REPOSITORY_NAMESPACE + NON_EXISTENT_PREDICATE); 119 verifyRejectUpdateLiteral(REPOSITORY_NAMESPACE + NON_EXISTENT_PREDICATE); 120 121 // Verify that types in this namespace are rejected 122 verifyRejectRdfType(FEDORA_CONTAINER.getURI()); 123 verifyRejectUpdateRdfType(FEDORA_CONTAINER.getURI()); 124 verifyRejectRdfType(REPOSITORY_NAMESPACE + NON_EXISTENT_TYPE); 125 verifyRejectUpdateRdfType(REPOSITORY_NAMESPACE + NON_EXISTENT_TYPE); 126 } 127 128 @Test 129 public void testMementoNamespace() throws Exception { 130 // Verify rejection of known property 131 verifyRejectLiteral(MEMENTO_NAMESPACE + "mementoDatetime"); 132 verifyRejectUpdateLiteral(MEMENTO_NAMESPACE + "mementoDatetime"); 133 // Verify rejection of non-existent property 134 verifyRejectLiteral(MEMENTO_NAMESPACE + NON_EXISTENT_PREDICATE); 135 verifyRejectUpdateLiteral(MEMENTO_NAMESPACE + NON_EXISTENT_PREDICATE); 136 137 // Verify rejection of known type 138 verifyRejectRdfType(MEMENTO_TYPE); 139 verifyRejectUpdateRdfType(MEMENTO_TYPE); 140 // Verify rejection of non-existent type 141 verifyRejectRdfType(MEMENTO_NAMESPACE + NON_EXISTENT_TYPE); 142 verifyRejectUpdateRdfType(MEMENTO_NAMESPACE + NON_EXISTENT_TYPE); 143 } 144 145 private void verifyRejectRdfType(final String typeURI) throws Exception { 146 verifyRejectUriRef(RDF_NAMESPACE + "type", typeURI); 147 } 148 149 private void verifyRejectUriRef(final String predicate, final String refURI) throws Exception { 150 final String pid = getRandomUniqueId(); 151 final String content = "<> <" + predicate + "> <" + refURI + "> ."; 152 try (final CloseableHttpResponse response = execute(putObjMethod(pid, "text/turtle", content))) { 153 assertEquals("Must reject server managed property <" + predicate + "> <" + refURI + ">", 154 409, response.getStatusLine().getStatusCode()); 155 } 156 } 157 158 private void verifyRejectLiteral(final String predicate) throws Exception { 159 final String pid = getRandomUniqueId(); 160 final String content = "<> <" + predicate + "> \"value\" ."; 161 try (final CloseableHttpResponse response = execute(putObjMethod(pid, "text/turtle", content))) { 162 assertEquals("Must reject server managed property <" + predicate + ">", 163 409, response.getStatusLine().getStatusCode()); 164 } 165 } 166 167 private void verifyRejectUpdateLiteral(final String predicate) throws Exception { 168 final String updateString = 169 "INSERT { <> <" + predicate + "> \"value\" } WHERE { }"; 170 171 final String pid = getRandomUniqueId(); 172 createObject(pid); 173 try (final CloseableHttpResponse response = performUpdate(pid, updateString)) { 174 assertEquals("Must reject update of server managed property <" + predicate + ">", 175 409, response.getStatusLine().getStatusCode()); 176 } 177 } 178 179 private void verifyRejectUpdateRdfType(final String typeURI) throws Exception { 180 verifyRejectUpdateUriRef(RDF_NAMESPACE + "type", typeURI); 181 } 182 183 private void verifyRejectUpdateUriRef(final String predicate, final String refURI) throws Exception { 184 final String updateString = 185 "INSERT { <> <" + predicate + "> <" + refURI + "> } WHERE { }"; 186 187 final String pid = getRandomUniqueId(); 188 createObject(pid); 189 try (final CloseableHttpResponse response = performUpdate(pid, updateString)) { 190 assertEquals("Must reject update of server managed property <" + predicate + "> <" + refURI + ">", 191 409, response.getStatusLine().getStatusCode()); 192 } 193 } 194 195 private CloseableHttpResponse performUpdate(final String pid, final String updateString) throws Exception { 196 final HttpPatch patchProp = patchObjMethod(pid); 197 patchProp.setHeader(CONTENT_TYPE, "application/sparql-update"); 198 patchProp.setEntity(new StringEntity(updateString)); 199 return execute(patchProp); 200 } 201 202 @Test 203 public void testNonRdfSourceServerGeneratedTriples() throws Exception { 204 final String pid = getRandomUniqueId(); 205 final String describedPid = pid + "/" + FCR_METADATA; 206 final String location = serverAddress + pid; 207 208 final String filename = "some-file.txt"; 209 final String content = "this is the content"; 210 createBinary(pid, filename, TEXT_PLAIN, content); 211 212 final Model model = getModel(describedPid); 213 214 // verify properties initially generated 215 final Resource resc = model.getResource(location); 216 assertEquals(content.length(), resc.getProperty(HAS_SIZE).getLong()); 217 assertEquals(serverAddress + describedPid, resc.getProperty(DESCRIBED_BY).getResource().getURI()); 218 assertEquals("text/plain", resc.getProperty(HAS_MIME_TYPE).getString()); 219 assertEquals(filename, resc.getProperty(HAS_ORIGINAL_NAME).getString()); 220 221 // verify properties can be deleted 222 // iana:describedby cannot be totally removed since it is added in the response 223 verifyDeleteExistingProperty(describedPid, location, HAS_SIZE, 224 resc.getProperty(HAS_SIZE).getObject()); 225 verifyDeleteExistingProperty(describedPid, location, HAS_MIME_TYPE, 226 resc.getProperty(HAS_MIME_TYPE).getObject()); 227 verifyDeleteExistingProperty(describedPid, location, HAS_ORIGINAL_NAME, 228 resc.getProperty(HAS_ORIGINAL_NAME).getObject()); 229 230 // verify property can be added 231 verifySetProperty(describedPid, location, DESCRIBED_BY, createResource("http://example.com")); 232 verifySetProperty(describedPid, location, HAS_SIZE, model.createTypedLiteral(99L)); 233 verifySetProperty(describedPid, location, HAS_MIME_TYPE, model.createLiteral("text/special")); 234 verifySetProperty(describedPid, location, HAS_ORIGINAL_NAME, model.createLiteral("different.txt")); 235 236 // Verify deletion of added describedby 237 verifyDeleteExistingProperty(describedPid, location, DESCRIBED_BY, 238 createResource("http://example.com")); 239 } 240 241 private void createBinary(final String pid, final String filename, final String contentType, final String content) 242 throws Exception { 243 final HttpPost post = new HttpPost(serverAddress); 244 post.setEntity(new StringEntity(content)); 245 post.setHeader("Slug", pid); 246 post.setHeader(CONTENT_TYPE, contentType); 247 post.setHeader(CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\""); 248 post.setHeader(LINK, NON_RDF_SOURCE_LINK_HEADER); 249 try (final CloseableHttpResponse response = execute(post)) { 250 assertEquals(CREATED.getStatusCode(), getStatus(response)); 251 } 252 } 253 254 private void verifyDeleteExistingProperty(final String pid, final String subjectURI, 255 final Property property, final RDFNode object) throws Exception { 256 final String value = rdfNodeToString(object); 257 final String deleteString = 258 "DELETE { <> <" + property.getURI() + "> " + value + " } WHERE { }"; 259 260 performUpdate(pid, deleteString).close(); 261 262 final Model resultModel = getModel(pid); 263 final Resource resultResc = resultModel.getResource(subjectURI); 264 assertFalse("Must not contain deleted property " + property, resultResc.hasProperty(property, object)); 265 } 266 267 private void verifySetProperty(final String pid, final String subjectURI, final Property property, 268 final RDFNode object) throws Exception { 269 final String value = rdfNodeToString(object); 270 final String updateString = 271 "INSERT { <> <" + property.getURI() + "> " + value + " } WHERE { }"; 272 273 performUpdate(pid, updateString).close(); 274 275 final Model model = getModel(pid); 276 final Resource resc = model.getResource(subjectURI); 277 assertTrue("Must contain updated property " + property, resc.hasProperty(property, object)); 278 } 279 280 private String rdfNodeToString(final RDFNode object) { 281 String value; 282 if (object.isLiteral()) { 283 final Literal literal = object.asLiteral(); 284 value = "\"" + literal.getValue().toString() + "\""; 285 if (!literal.getDatatype().equals(XSDDatatype.XSDstring)) { 286 value += "^^<" + literal.getDatatypeURI() + ">"; 287 } 288 } else { 289 value = "<" + object.toString() + ">"; 290 } 291 return value; 292 } 293}