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        final FcrepoResponse getResponse = client.get(response.getLocation()).perform();
112        final Map<String, String> contentDisp = getResponse.getContentDisposition();
113        assertEquals(filename, contentDisp.get(CONTENT_DISPOSITION_FILENAME));
114
115        assertEquals(mimetype, getResponse.getContentType());
116
117        final String getContent = IOUtils.toString(getResponse.getBody(), "UTF-8");
118        assertEquals(bodyContent, getContent);
119    }
120
121    @Test
122    public void testPostDigestMismatch() throws Exception {
123        final String bodyContent = "Hello world";
124        final String invalidDigest = "adc83b19e793491b1c6ea0fd8b46cd9f32e592fc";
125
126        final FcrepoResponse response = client.post(new URI(serverAddress))
127                .body(new ByteArrayInputStream(bodyContent.getBytes()), "text/plain")
128                .digest(invalidDigest)
129                .perform();
130
131        assertEquals("Invalid checksum was not rejected", CONFLICT.getStatusCode(), response.getStatusCode());
132    }
133
134    @Test
135    public void testPut() throws Exception {
136        final FcrepoResponse response = create();
137
138        assertEquals(CREATED.getStatusCode(), response.getStatusCode());
139        assertEquals(url, response.getLocation());
140    }
141
142    @Test
143    public void testPutEtag() throws Exception {
144        // Create object
145        final FcrepoResponse response = create();
146
147        // Get the etag of the nearly created object
148        final String etag = response.getHeaderValue(ETAG);
149
150        // Retrieve the body of the resource so we can modify it
151        String body = getTurtle(url);
152        body += "\n<> dc:title \"some-title\"";
153
154        // Check that etag is making it through and being rejected
155        final FcrepoResponse updateResp = client.put(url)
156                .body(new ByteArrayInputStream(body.getBytes()), TEXT_TURTLE)
157                .ifMatch("\"bad-etag\"")
158                .perform();
159
160        assertEquals(PRECONDITION_FAILED.getStatusCode(), updateResp.getStatusCode());
161
162        // Verify that etag is retrieved and resubmitted correctly
163        final FcrepoResponse validResp = client.put(url)
164                .body(new ByteArrayInputStream(body.getBytes()), TEXT_TURTLE)
165                .ifMatch(etag)
166                .perform();
167        assertEquals(NO_CONTENT.getStatusCode(), validResp.getStatusCode());
168    }
169
170    @Test
171    public void testPutUnmodifiedSince() throws Exception {
172        // Create object
173        final FcrepoResponse response = create();
174
175        // Retrieve the body of the resource so we can modify it
176        String body = getTurtle(url);
177        body += "\n<> dc:title \"some-title\"";
178
179        // Update the body the first time, which should succeed
180        final String originalModified = response.getHeaderValue(LAST_MODIFIED);
181        final FcrepoResponse matchResponse = client.put(url)
182                .body(new ByteArrayInputStream(body.getBytes()), TEXT_TURTLE)
183                .ifUnmodifiedSince(originalModified)
184                .perform();
185
186        assertEquals(NO_CONTENT.getStatusCode(), matchResponse.getStatusCode());
187
188        // Update the triples a second time with old timestamp
189        final FcrepoResponse mismatchResponse = client.put(url)
190                .body(new ByteArrayInputStream(body.getBytes()), TEXT_TURTLE)
191                .ifUnmodifiedSince(UNMODIFIED_DATE)
192                .perform();
193
194        assertEquals(PRECONDITION_FAILED.getStatusCode(), mismatchResponse.getStatusCode());
195    }
196
197    @Test
198    public void testPatch() throws Exception {
199        // Create object
200        final FcrepoResponse createResp = create();
201        final String createdEtag = createResp.getHeaderValue(ETAG);
202
203        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
204
205        // Update triples with sparql update
206        final FcrepoResponse response = client.patch(url)
207                .body(body)
208                .ifMatch(createdEtag)
209                .perform();
210
211        final String updateEtag = response.getHeaderValue(ETAG);
212
213        assertEquals(NO_CONTENT.getStatusCode(), response.getStatusCode());
214        assertNotEquals("Etag did not change after patch", createdEtag, updateEtag);
215    }
216
217    @Test
218    public void testPatchNoBody() throws Exception {
219        create();
220
221        final FcrepoResponse response = client.patch(url)
222                .perform();
223
224        assertEquals(BAD_REQUEST.getStatusCode(), response.getStatusCode());
225    }
226
227    @Test
228    public void testDelete() throws Exception {
229        create();
230
231        final FcrepoResponse response = client.delete(url).perform();
232
233        assertEquals(NO_CONTENT.getStatusCode(), response.getStatusCode());
234
235        assertEquals(GONE.getStatusCode(), client.get(url).perform().getStatusCode());
236    }
237
238    @Test
239    public void testGet() throws Exception {
240        create();
241        final FcrepoResponse response = client.get(url).perform();
242
243        assertEquals(OK.getStatusCode(), response.getStatusCode());
244    }
245
246    @Test
247    public void testGetNotFound() throws Exception {
248        final FcrepoResponse response = client.get(url).perform();
249
250        assertEquals(NOT_FOUND.getStatusCode(), response.getStatusCode());
251    }
252
253    @Test
254    public void testGetUnmodified() throws Exception {
255        // Check that get returns a 304 if the item hasn't changed according to last-modified/etag
256        final FcrepoResponse response = create();
257
258        // Get tomorrows date to provide as the modified-since date
259        final String lastModified = response.getHeaderValue(LAST_MODIFIED);
260        final Date modDate = DateUtils.parseDate(lastModified);
261        final Calendar cal = Calendar.getInstance();
262        cal.setTime(modDate);
263        cal.add(Calendar.DATE, 1);
264
265        final FcrepoResponse modResp = client.get(url)
266                .ifModifiedSince(DateUtils.formatDate(cal.getTime()))
267                .perform();
268
269        assertEquals(NOT_MODIFIED.getStatusCode(), modResp.getStatusCode());
270
271        final String originalEtag = response.getHeaderValue(ETAG);
272        final FcrepoResponse etagResp = client.get(url)
273                .ifNoneMatch(originalEtag)
274                .perform();
275        assertEquals(NOT_MODIFIED.getStatusCode(), etagResp.getStatusCode());
276    }
277
278    @Test
279    public void testGetAccept() throws Exception {
280        // Check that get returns a 304 if the item hasn't changed according to last-modified/etag
281        create();
282
283        final FcrepoResponse response = client.get(url)
284                .accept("application/n-triples")
285                .perform();
286
287        assertEquals("application/n-triples", response.getHeaderValue(CONTENT_TYPE));
288        assertEquals(OK.getStatusCode(), response.getStatusCode());
289    }
290
291    @Test
292    public void testGetPrefer() throws Exception {
293        // Check that get returns a 304 if the item hasn't changed according to last-modified/etag
294        create();
295
296        final FcrepoResponse response = client.get(url)
297                .preferMinimal()
298                .perform();
299
300        assertEquals(OK.getStatusCode(), response.getStatusCode());
301        assertEquals("return=minimal", response.getHeaderValue("Preference-Applied"));
302    }
303
304    @Test
305    public void testGetRange() throws Exception {
306        // Creating a binary for retrieval
307        final String mimetype = "text/plain";
308        final String bodyContent = "Hello world";
309        final FcrepoResponse response = client.post(new URI(serverAddress))
310                .body(new ByteArrayInputStream(bodyContent.getBytes()), mimetype)
311                .perform();
312
313        final URI url = response.getLocation();
314
315        // Get the content of the object after the first 6 bytes
316        final FcrepoResponse rangeResp = client.get(url)
317                .range(6L, null)
318                .perform();
319
320        final String content = IOUtils.toString(rangeResp.getBody(), "UTF-8");
321        assertEquals("Body did not contain correct range of original content", "world", content);
322        assertEquals(PARTIAL_CONTENT.getStatusCode(), rangeResp.getStatusCode());
323    }
324
325    @Test
326    public void testHead() throws Exception {
327        final FcrepoResponse response = create();
328        final FcrepoResponse headResp = client.head(url).perform();
329
330        assertEquals(OK.getStatusCode(), headResp.getStatusCode());
331        assertEquals(response.getHeaderValue(ETAG), headResp.getHeaderValue(ETAG));
332        assertNotNull(headResp.getHeaderValue("Allow"));
333    }
334
335    @Test
336    public void testOptions() throws Exception {
337        create();
338        final FcrepoResponse headResp = client.options(url).perform();
339
340        assertEquals(OK.getStatusCode(), headResp.getStatusCode());
341        assertNotNull(headResp.getHeaderValue("Allow"));
342        assertNotNull(headResp.getHeaderValue("Accept-Post"));
343        assertNotNull(headResp.getHeaderValue("Accept-Patch"));
344    }
345
346    @Test
347    public void testMove() throws Exception {
348        create();
349
350        final URI destUrl = new URI(url.toString() + "_dest");
351        final FcrepoResponse moveResp = client.move(url, destUrl).perform();
352        assertEquals(CREATED.getStatusCode(), moveResp.getStatusCode());
353
354        assertEquals("Object still at original url",
355                GONE.getStatusCode(), client.get(url).perform().getStatusCode());
356
357        assertEquals("Object not at expected new url",
358                OK.getStatusCode(), client.get(destUrl).perform().getStatusCode());
359    }
360
361    @Test
362    public void testCopy() throws Exception {
363        create();
364
365        // Add something identifiable to the record
366        final InputStream body = new ByteArrayInputStream(sparqlUpdate.getBytes());
367        client.patch(url).body(body).perform();
368
369        final URI destUrl = new URI(url.toString() + "_dest");
370        final FcrepoResponse copyResp = client.copy(url, destUrl).perform();
371        assertEquals(CREATED.getStatusCode(), copyResp.getStatusCode());
372
373        final FcrepoResponse originalResp = client.get(url).perform();
374        final String originalContent = IOUtils.toString(originalResp.getBody(), "UTF-8");
375        assertTrue(originalContent.contains("Foo"));
376
377        final FcrepoResponse destResp = client.get(destUrl).perform();
378        final String destContent = IOUtils.toString(destResp.getBody(), "UTF-8");
379        assertTrue(destContent.contains("Foo"));
380    }
381
382    private FcrepoResponse create() throws FcrepoOperationFailedException {
383        return client.put(url).perform();
384    }
385
386    private String getTurtle(final URI url) throws Exception {
387        final FcrepoResponse getResponse = client.get(url)
388                .accept("text/turtle")
389                .perform();
390        return IOUtils.toString(getResponse.getBody(), "UTF-8");
391    }
392}