001/**
002 * Copyright 2015 DuraSpace, Inc.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.fcrepo.client.integration;
018
019import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
020import static javax.ws.rs.core.Response.Status.CONFLICT;
021import static javax.ws.rs.core.Response.Status.CREATED;
022import static javax.ws.rs.core.Response.Status.GONE;
023import static javax.ws.rs.core.Response.Status.NOT_FOUND;
024import static javax.ws.rs.core.Response.Status.NOT_MODIFIED;
025import static javax.ws.rs.core.Response.Status.NO_CONTENT;
026import static javax.ws.rs.core.Response.Status.OK;
027import static javax.ws.rs.core.Response.Status.PARTIAL_CONTENT;
028import static javax.ws.rs.core.Response.Status.PRECONDITION_FAILED;
029import static org.fcrepo.client.FedoraHeaderConstants.CONTENT_DISPOSITION_FILENAME;
030import static org.fcrepo.client.FedoraHeaderConstants.CONTENT_TYPE;
031import static org.fcrepo.client.FedoraHeaderConstants.ETAG;
032import static org.fcrepo.client.FedoraHeaderConstants.LAST_MODIFIED;
033import static org.fcrepo.client.TestUtils.TEXT_TURTLE;
034import static org.fcrepo.client.TestUtils.sparqlUpdate;
035import static org.junit.Assert.assertEquals;
036import static org.junit.Assert.assertNotEquals;
037import static org.junit.Assert.assertNotNull;
038import static org.junit.Assert.assertTrue;
039
040import java.io.ByteArrayInputStream;
041import java.io.InputStream;
042import java.net.URI;
043import java.util.Calendar;
044import java.util.Date;
045import java.util.Map;
046
047import org.apache.commons.io.IOUtils;
048import org.apache.http.client.utils.DateUtils;
049import org.fcrepo.client.FcrepoClient;
050import org.fcrepo.client.FcrepoOperationFailedException;
051import org.fcrepo.client.FcrepoResponse;
052import org.jgroups.util.UUID;
053import org.junit.Before;
054import org.junit.Test;
055import org.junit.runner.RunWith;
056import org.springframework.test.context.ContextConfiguration;
057import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
058
059/**
060 * @author bbpennel
061 */
062@RunWith(SpringJUnit4ClassRunner.class)
063@ContextConfiguration("/spring-test/test-container.xml")
064public class FcrepoClientIT extends AbstractResourceIT {
065
066    protected URI url;
067
068    final private String UNMODIFIED_DATE = "Mon, 1 Jan 2001 00:00:00 GMT";
069
070    public FcrepoClientIT() throws Exception {
071        super();
072
073        client = FcrepoClient.client()
074                .credentials("fedoraAdmin", "password")
075                .authScope("localhost")
076                .build();
077    }
078
079    @Before
080    public void before() {
081        url = URI.create(serverAddress + UUID.randomUUID().toString());
082    }
083
084    @Test
085    public void testPost() throws Exception {
086        final FcrepoResponse response = client.post(new URI(serverAddress))
087                .perform();
088
089        assertEquals(CREATED.getStatusCode(), response.getStatusCode());
090    }
091
092    @Test
093    public void testPostBinary() throws Exception {
094        final String slug = "hello1";
095        final String filename = "hello.txt";
096        final String mimetype = "text/plain";
097        final String bodyContent = "Hello world";
098        final FcrepoResponse response = client.post(new URI(serverAddress))
099                .body(new ByteArrayInputStream(bodyContent.getBytes()), mimetype)
100                .filename(filename)
101                .slug(slug)
102                .perform();
103
104        final String content = IOUtils.toString(response.getBody(), "UTF-8");
105        final int status = response.getStatusCode();
106
107        assertEquals("Didn't get a CREATED response! Got content:\n" + content,
108                CREATED.getStatusCode(), status);
109        assertEquals("Location did not match slug", serverAddress + slug, response.getLocation().toString());
110
111        assertNotNull("Didn't find linked description!", response.getLinkHeaders("describedby").get(0));
112
113        final FcrepoResponse getResponse = client.get(response.getLocation()).perform();
114        final Map<String, String> contentDisp = getResponse.getContentDisposition();
115        assertEquals(filename, contentDisp.get(CONTENT_DISPOSITION_FILENAME));
116
117        assertEquals(mimetype, getResponse.getContentType());
118
119        final String getContent = IOUtils.toString(getResponse.getBody(), "UTF-8");
120        assertEquals(bodyContent, getContent);
121    }
122
123    @Test
124    public void testPostDigestMismatch() throws Exception {
125        final String bodyContent = "Hello world";
126        final String invalidDigest = "adc83b19e793491b1c6ea0fd8b46cd9f32e592fc";
127
128        final FcrepoResponse response = client.post(new URI(serverAddress))
129                .body(new ByteArrayInputStream(bodyContent.getBytes()), "text/plain")
130                .digest(invalidDigest)
131                .perform();
132
133        assertEquals("Invalid checksum was not rejected", CONFLICT.getStatusCode(), response.getStatusCode());
134    }
135
136    @Test
137    public void testPut() throws Exception {
138        final FcrepoResponse response = create();
139
140        assertEquals(CREATED.getStatusCode(), response.getStatusCode());
141        assertEquals(url, response.getLocation());
142    }
143
144    @Test
145    public void testPutEtag() throws Exception {
146        // Create object
147        final FcrepoResponse response = create();
148
149        // Get the etag of the nearly created object
150        final String etag = response.getHeaderValue(ETAG);
151
152        // Retrieve the body of the resource so we can modify it
153        String body = getTurtle(url);
154        body += "\n<> dc:title \"some-title\"";
155
156        // Check that etag is making it through and being rejected
157        final FcrepoResponse updateResp = client.put(url)
158                .body(new ByteArrayInputStream(body.getBytes()), TEXT_TURTLE)
159                .ifMatch("\"bad-etag\"")
160                .perform();
161
162        assertEquals(PRECONDITION_FAILED.getStatusCode(), updateResp.getStatusCode());
163
164        // Verify that etag is retrieved and resubmitted correctly
165        final FcrepoResponse validResp = client.put(url)
166                .body(new ByteArrayInputStream(body.getBytes()), TEXT_TURTLE)
167                .ifMatch(etag)
168                .perform();
169        assertEquals(NO_CONTENT.getStatusCode(), validResp.getStatusCode());
170    }
171
172    @Test
173    public void testPutUnmodifiedSince() throws Exception {
174        // Create object
175        final FcrepoResponse response = create();
176
177        // Retrieve the body of the resource so we can modify it
178        String body = getTurtle(url);
179        body += "\n<> dc:title \"some-title\"";
180
181        // Update the body the first time, which should succeed
182        final String originalModified = response.getHeaderValue(LAST_MODIFIED);
183        final FcrepoResponse matchResponse = client.put(url)
184                .body(new ByteArrayInputStream(body.getBytes()), TEXT_TURTLE)
185                .ifUnmodifiedSince(originalModified)
186                .perform();
187
188        assertEquals(NO_CONTENT.getStatusCode(), matchResponse.getStatusCode());
189
190        // Update the triples a second time with old timestamp
191        final FcrepoResponse mismatchResponse = client.put(url)
192                .body(new ByteArrayInputStream(body.getBytes()), TEXT_TURTLE)
193                .ifUnmodifiedSince(UNMODIFIED_DATE)
194                .perform();
195
196        assertEquals(PRECONDITION_FAILED.getStatusCode(), mismatchResponse.getStatusCode());
197    }
198
199    @Test
200    public void testPutLenient() throws Exception {
201        // Create object
202        final FcrepoResponse response = create();
203        final String body = "<> <http://purl.org/dc/elements/1.1/title> \"some-title\"";
204
205        // try to update without lenient header
206        final FcrepoResponse strictResponse = client.put(url)
207                .body(new ByteArrayInputStream(body.getBytes()), TEXT_TURTLE)
208                .perform();
209        assertEquals(CONFLICT.getStatusCode(), strictResponse.getStatusCode());
210
211        // try again with lenient header
212        final FcrepoResponse lenientResponse = client.put(url)
213                .body(new ByteArrayInputStream(body.getBytes()), TEXT_TURTLE)
214                .preferLenient()
215                .perform();
216        assertEquals(NO_CONTENT.getStatusCode(), lenientResponse.getStatusCode());
217    }
218
219    @Test
220    public void testPatch() throws Exception {
221        // Create object
222        final FcrepoResponse createResp = create();
223        final String createdEtag = createResp.getHeaderValue(ETAG);
224
225        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
226
227        // Update triples with sparql update
228        final FcrepoResponse response = client.patch(url)
229                .body(body)
230                .ifMatch(createdEtag)
231                .perform();
232
233        final String updateEtag = response.getHeaderValue(ETAG);
234
235        assertEquals(NO_CONTENT.getStatusCode(), response.getStatusCode());
236        assertNotEquals("Etag did not change after patch", createdEtag, updateEtag);
237    }
238
239    @Test
240    public void testPatchNoBody() throws Exception {
241        create();
242
243        final FcrepoResponse response = client.patch(url)
244                .perform();
245
246        assertEquals(BAD_REQUEST.getStatusCode(), response.getStatusCode());
247    }
248
249    @Test
250    public void testDelete() throws Exception {
251        create();
252
253        final FcrepoResponse response = client.delete(url).perform();
254
255        assertEquals(NO_CONTENT.getStatusCode(), response.getStatusCode());
256
257        assertEquals(GONE.getStatusCode(), client.get(url).perform().getStatusCode());
258    }
259
260    @Test
261    public void testGet() throws Exception {
262        create();
263        final FcrepoResponse response = client.get(url).perform();
264
265        assertEquals(OK.getStatusCode(), response.getStatusCode());
266    }
267
268    @Test
269    public void testGetNotFound() throws Exception {
270        final FcrepoResponse response = client.get(url).perform();
271
272        assertEquals(NOT_FOUND.getStatusCode(), response.getStatusCode());
273    }
274
275    @Test
276    public void testGetUnmodified() throws Exception {
277        // Check that get returns a 304 if the item hasn't changed according to last-modified/etag
278        final FcrepoResponse response = create();
279
280        // Get tomorrows date to provide as the modified-since date
281        final String lastModified = response.getHeaderValue(LAST_MODIFIED);
282        final Date modDate = DateUtils.parseDate(lastModified);
283        final Calendar cal = Calendar.getInstance();
284        cal.setTime(modDate);
285        cal.add(Calendar.DATE, 1);
286
287        final FcrepoResponse modResp = client.get(url)
288                .ifModifiedSince(DateUtils.formatDate(cal.getTime()))
289                .perform();
290
291        assertEquals(NOT_MODIFIED.getStatusCode(), modResp.getStatusCode());
292
293        final String originalEtag = response.getHeaderValue(ETAG);
294        final FcrepoResponse etagResp = client.get(url)
295                .ifNoneMatch(originalEtag)
296                .perform();
297        assertEquals(NOT_MODIFIED.getStatusCode(), etagResp.getStatusCode());
298    }
299
300    @Test
301    public void testGetAccept() throws Exception {
302        // Check that get returns a 304 if the item hasn't changed according to last-modified/etag
303        create();
304
305        final FcrepoResponse response = client.get(url)
306                .accept("application/n-triples")
307                .perform();
308
309        assertEquals("application/n-triples", response.getHeaderValue(CONTENT_TYPE));
310        assertEquals(OK.getStatusCode(), response.getStatusCode());
311    }
312
313    @Test
314    public void testGetPrefer() throws Exception {
315        // Check that get returns a 304 if the item hasn't changed according to last-modified/etag
316        create();
317
318        final FcrepoResponse response = client.get(url)
319                .preferMinimal()
320                .perform();
321
322        assertEquals(OK.getStatusCode(), response.getStatusCode());
323        assertEquals("return=minimal", response.getHeaderValue("Preference-Applied"));
324    }
325
326    @Test
327    public void testGetRange() throws Exception {
328        // Creating a binary for retrieval
329        final String mimetype = "text/plain";
330        final String bodyContent = "Hello world";
331        final FcrepoResponse response = client.post(new URI(serverAddress))
332                .body(new ByteArrayInputStream(bodyContent.getBytes()), mimetype)
333                .perform();
334
335        final URI url = response.getLocation();
336
337        // Get the content of the object after the first 6 bytes
338        final FcrepoResponse rangeResp = client.get(url)
339                .range(6L, null)
340                .perform();
341
342        final String content = IOUtils.toString(rangeResp.getBody(), "UTF-8");
343        assertEquals("Body did not contain correct range of original content", "world", content);
344        assertEquals(PARTIAL_CONTENT.getStatusCode(), rangeResp.getStatusCode());
345    }
346
347    @Test
348    public void testGetDisableRedirects() throws Exception {
349        // Creating a binary with external content for retrieval
350        final String mimetype = "message/external-body; access-type=URL; URL=\"http://www.example.com/file\"";
351        final FcrepoResponse response = client.post(new URI(serverAddress))
352                .body(new ByteArrayInputStream(new byte[]{}), mimetype)
353                .perform();
354
355        final URI url = response.getLocation();
356
357        // Make sure the response is the redirect itself, not the URL being redirected to
358        final FcrepoResponse getResponse = client.get(url).disableRedirects().perform();
359        assertEquals(307, getResponse.getStatusCode());
360        assertEquals(url, getResponse.getUrl());
361        assertEquals(URI.create("http://www.example.com/file"), getResponse.getLocation());
362    }
363
364    @Test
365    public void testHead() throws Exception {
366        final FcrepoResponse response = create();
367        final FcrepoResponse headResp = client.head(url).perform();
368
369        assertEquals(OK.getStatusCode(), headResp.getStatusCode());
370        assertEquals(response.getHeaderValue(ETAG), headResp.getHeaderValue(ETAG));
371        assertNotNull(headResp.getHeaderValue("Allow"));
372    }
373
374    @Test
375    public void testOptions() throws Exception {
376        create();
377        final FcrepoResponse headResp = client.options(url).perform();
378
379        assertEquals(OK.getStatusCode(), headResp.getStatusCode());
380        assertNotNull(headResp.getHeaderValue("Allow"));
381        assertNotNull(headResp.getHeaderValue("Accept-Post"));
382        assertNotNull(headResp.getHeaderValue("Accept-Patch"));
383    }
384
385    @Test
386    public void testMove() throws Exception {
387        create();
388
389        final URI destUrl = new URI(url.toString() + "_dest");
390        final FcrepoResponse moveResp = client.move(url, destUrl).perform();
391        assertEquals(CREATED.getStatusCode(), moveResp.getStatusCode());
392
393        assertEquals("Object still at original url",
394                GONE.getStatusCode(), client.get(url).perform().getStatusCode());
395
396        assertEquals("Object not at expected new url",
397                OK.getStatusCode(), client.get(destUrl).perform().getStatusCode());
398    }
399
400    @Test
401    public void testCopy() throws Exception {
402        create();
403
404        // Add something identifiable to the record
405        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
406        client.patch(url).body(body).perform();
407
408        final URI destUrl = new URI(url.toString() + "_dest");
409        final FcrepoResponse copyResp = client.copy(url, destUrl).perform();
410        assertEquals(CREATED.getStatusCode(), copyResp.getStatusCode());
411
412        final FcrepoResponse originalResp = client.get(url).perform();
413        final String originalContent = IOUtils.toString(originalResp.getBody(), "UTF-8");
414        assertTrue(originalContent.contains("Foo"));
415
416        final FcrepoResponse destResp = client.get(destUrl).perform();
417        final String destContent = IOUtils.toString(destResp.getBody(), "UTF-8");
418        assertTrue(destContent.contains("Foo"));
419    }
420
421    private FcrepoResponse create() throws FcrepoOperationFailedException {
422        return client.put(url).perform();
423    }
424
425    private String getTurtle(final URI url) throws Exception {
426        final FcrepoResponse getResponse = client.get(url)
427                .accept("text/turtle")
428                .perform();
429        return IOUtils.toString(getResponse.getBody(), "UTF-8");
430    }
431}