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