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}