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.http.api;
019
020import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE;
021import static java.nio.charset.StandardCharsets.UTF_8;
022import static javax.ws.rs.core.HttpHeaders.CONTENT_DISPOSITION;
023import static javax.ws.rs.core.HttpHeaders.LINK;
024import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
025import static javax.ws.rs.core.Response.Status.CONFLICT;
026import static javax.ws.rs.core.Response.Status.CREATED;
027import static javax.ws.rs.core.Response.Status.NO_CONTENT;
028import static javax.ws.rs.core.Response.Status.OK;
029import static javax.ws.rs.core.Response.Status.TEMPORARY_REDIRECT;
030import static org.fcrepo.kernel.api.RdfLexicon.NON_RDF_SOURCE;
031import static org.junit.Assert.assertEquals;
032import static org.junit.Assert.assertNull;
033import static org.junit.Assert.assertTrue;
034import java.io.File;
035import java.io.FileWriter;
036import java.io.IOException;
037
038import static org.apache.http.HttpStatus.SC_OK;
039import static org.apache.http.HttpHeaders.CONTENT_LENGTH;
040import static org.apache.http.HttpStatus.SC_CREATED;
041
042import org.apache.commons.io.IOUtils;
043import org.apache.commons.lang3.StringUtils;
044import org.apache.http.client.config.RequestConfig;
045import org.apache.http.client.methods.CloseableHttpResponse;
046import org.apache.http.client.methods.HttpDelete;
047import org.apache.http.client.methods.HttpGet;
048import org.apache.http.client.methods.HttpHead;
049import org.apache.http.client.methods.HttpPost;
050import org.apache.http.client.methods.HttpPut;
051import org.apache.http.client.methods.HttpRequestBase;
052import org.apache.http.client.methods.HttpUriRequest;
053import org.apache.http.entity.StringEntity;
054import org.apache.http.impl.client.CloseableHttpClient;
055import org.apache.http.impl.client.HttpClientBuilder;
056import org.fcrepo.kernel.api.FedoraTypes;
057import org.glassfish.jersey.media.multipart.ContentDisposition;
058import org.junit.Before;
059import org.junit.Rule;
060import org.junit.Test;
061import org.junit.rules.TemporaryFolder;
062import org.springframework.test.context.TestExecutionListeners;
063
064/**
065 * @author whikloj
066 * @since 2018-07-10
067 */
068@TestExecutionListeners(
069        listeners = { TestIsolationExecutionListener.class },
070        mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
071public class ExternalContentHandlerIT extends AbstractResourceIT {
072
073    @Rule
074    public TemporaryFolder tempFolder = new TemporaryFolder();
075
076    private static final String NON_RDF_SOURCE_LINK_HEADER = "<" + NON_RDF_SOURCE.getURI() + ">;rel=\"type\"";
077
078    private static final String WANT_DIGEST = "Want-Digest";
079
080    private static final String DIGEST = "Digest";
081
082    private static final String TEST_BINARY_CONTENT = "01234567890123456789012345678901234567890123456789";
083
084    private static final String TEST_SHA_DIGEST_HEADER_VALUE = "sha=9578f951955d37f20b601c26591e260c1e5389bf";
085
086    private static final String TEST_MD5_DIGEST_HEADER_VALUE = "md5=baed005300234f3d1503c50a48ce8e6f";
087
088    private static final CloseableHttpClient noFollowClient = HttpClientBuilder.create()
089            .disableRedirectHandling().build();
090
091    @Before
092    public void setup() throws Exception {
093        tempFolder.create();
094    }
095
096    @Test
097    public void testProxyRemoteContentTypeForHttpUri() throws Exception {
098        final var externalLocation = createHttpResource("audio/ogg", "xyz");
099        final String finalLocation = getRandomUniqueId();
100
101        // Make an external content resource proxying the above URI.
102        final HttpPut put = putObjMethod(finalLocation);
103        put.addHeader(LINK, getExternalContentLinkHeader(externalLocation, "proxy", null));
104        assertEquals(CREATED.getStatusCode(), getStatus(put));
105
106        // Get the external content proxy resource.
107        try (final CloseableHttpResponse response = execute(getObjMethod(finalLocation))) {
108            assertEquals(SC_OK, getStatus(response));
109            assertContentType(response, "audio/ogg");
110            assertContentLocation(response, externalLocation);
111            assertContentLength(response, 3);
112        }
113    }
114
115    @Test
116    public void testProxyClientContentTypeOverridesRemoteForHttpUri() throws Exception {
117        final var externalLocation = createHttpResource("audio/ogg", "vxyz");
118        final String finalLocation = getRandomUniqueId();
119
120        // Make an external content resource proxying the above URI.
121        final HttpPut put = putObjMethod(finalLocation);
122        put.addHeader(LINK, getExternalContentLinkHeader(externalLocation, "proxy", "audio/mp3"));
123        assertEquals(CREATED.getStatusCode(), getStatus(put));
124
125        // Get the external content proxy resource.
126        try (final CloseableHttpResponse response = execute(getObjMethod(finalLocation))) {
127            assertEquals(SC_OK, getStatus(response));
128            assertContentType(response, "audio/mp3");
129            assertContentLocation(response, externalLocation);
130            assertContentLength(response, 4);
131        }
132    }
133
134    @Test
135    public void testCopyRemoteContentTypeForHttpUri() throws Exception {
136        final var externalLocation = createHttpResource("audio/ogg", "xyz");
137        final String finalLocation = getRandomUniqueId();
138
139        // Make an external content resource proxying the above URI.
140        final HttpPut put = putObjMethod(finalLocation);
141        put.addHeader(LINK, getExternalContentLinkHeader(externalLocation, "copy", null));
142        assertEquals(CREATED.getStatusCode(), getStatus(put));
143
144        // Get the external content proxy resource.
145        try (final CloseableHttpResponse response = execute(getObjMethod(finalLocation))) {
146            assertEquals(SC_OK, getStatus(response));
147            assertContentType(response, "audio/ogg");
148            assertContentLength(response, 3);
149        }
150    }
151
152    @Test
153    public void testCopyClientContentTypeOverridesRemoteForHttpUri() throws Exception {
154        final var externalLocation = createHttpResource("audio/ogg", "xyz");
155        final String finalLocation = getRandomUniqueId();
156
157        // Make an external content resource proxying the above URI.
158        final HttpPut put = putObjMethod(finalLocation);
159        put.addHeader(LINK, getExternalContentLinkHeader(externalLocation, "copy", "audio/mp3"));
160        assertEquals(CREATED.getStatusCode(), getStatus(put));
161
162        // Get the external content proxy resource.
163        try (final CloseableHttpResponse response = execute(getObjMethod(finalLocation))) {
164            assertEquals(SC_OK, getStatus(response));
165            assertContentType(response, "audio/mp3");
166            assertContentLength(response, 3);
167        }
168    }
169
170    @Test
171    public void testProxyWithWantDigestForLocalFile() throws IOException {
172
173        final File externalFile = createExternalLocalFile(TEST_BINARY_CONTENT);
174
175        final String fileUri = externalFile.toURI().toString();
176
177        final String id = getRandomUniqueId();
178        final HttpPut put = putObjMethod(id);
179        put.addHeader(LINK, getExternalContentLinkHeader(fileUri, "proxy", "text/plain"));
180        assertEquals(CREATED.getStatusCode(), getStatus(put));
181
182        final String expectedDigestHeaderValue = TEST_SHA_DIGEST_HEADER_VALUE;
183
184        // HEAD request with Want-Digest
185        final HttpHead headObjMethod = headObjMethod(id);
186        headObjMethod.addHeader(WANT_DIGEST, "sha");
187        checkExternalDataStreamResponseHeader(headObjMethod, fileUri, expectedDigestHeaderValue);
188
189        // GET request with Want-Digest
190        final HttpGet getObjMethod = getObjMethod(id);
191        getObjMethod.addHeader(WANT_DIGEST, "sha");
192        checkExternalDataStreamResponseHeader(getObjMethod, fileUri, expectedDigestHeaderValue);
193    }
194
195    @Test
196    public void testCopyWithWantDigestForLocalFile() throws IOException {
197
198        final File externalFile = createExternalLocalFile(TEST_BINARY_CONTENT);
199
200        final String fileUri = externalFile.toURI().toString();
201
202        final String id = getRandomUniqueId();
203        final HttpPut put = putObjMethod(id);
204        put.addHeader(LINK, getExternalContentLinkHeader(fileUri, "copy", "text/plain"));
205        assertEquals(CREATED.getStatusCode(), getStatus(put));
206
207        final String expectedDigestHeaderValue = TEST_SHA_DIGEST_HEADER_VALUE;
208
209        // HEAD request with Want-Digest
210        final HttpHead headObjMethod = headObjMethod(id);
211        headObjMethod.addHeader(WANT_DIGEST, "sha");
212        checkExternalDataStreamResponseHeader(headObjMethod, null, expectedDigestHeaderValue);
213
214        // GET request with Want-Digest
215        final HttpGet getObjMethod = getObjMethod(id);
216        getObjMethod.addHeader(WANT_DIGEST, "sha");
217        checkExternalDataStreamResponseHeader(getObjMethod, null, expectedDigestHeaderValue);
218    }
219
220    @Test
221    public void testProxyWithWantDigestForHttpUri() throws Exception {
222
223        final String dsUrl = createHttpResource(TEST_BINARY_CONTENT);
224
225        final String id = getRandomUniqueId();
226        final HttpPut put = putObjMethod(id);
227        put.addHeader(LINK, getExternalContentLinkHeader(dsUrl, "proxy", "text/plain"));
228        assertEquals(CREATED.getStatusCode(), getStatus(put));
229
230        final String expectedDigestHeaderValue = TEST_SHA_DIGEST_HEADER_VALUE;
231
232        // HEAD request with Want-Digest
233        final HttpHead headObjMethod = headObjMethod(id);
234        headObjMethod.addHeader(WANT_DIGEST, "sha");
235        checkExternalDataStreamResponseHeader(headObjMethod, dsUrl, expectedDigestHeaderValue);
236
237        // GET request with Want-Digest
238        final HttpGet getObjMethod = getObjMethod(id);
239        getObjMethod.addHeader(WANT_DIGEST, "sha");
240        checkExternalDataStreamResponseHeader(getObjMethod, dsUrl, expectedDigestHeaderValue);
241    }
242
243    @Test
244    public void testCopyWithWantDigestForHttpUri() throws Exception {
245
246        final String dsUrl = createHttpResource(TEST_BINARY_CONTENT);
247
248        final String id = getRandomUniqueId();
249        final HttpPut put = putObjMethod(id);
250        put.addHeader(LINK, getExternalContentLinkHeader(dsUrl, "copy", "text/plain"));
251        assertEquals(CREATED.getStatusCode(), getStatus(put));
252
253        final String expectedDigestHeaderValue = TEST_SHA_DIGEST_HEADER_VALUE;
254
255        // HEAD request with Want-Digest
256        final HttpHead headObjMethod = headObjMethod(id);
257        headObjMethod.addHeader(WANT_DIGEST, "sha");
258        checkExternalDataStreamResponseHeader(headObjMethod, null, expectedDigestHeaderValue);
259
260        // GET request with Want-Digest
261        final HttpGet getObjMethod = getObjMethod(id);
262        getObjMethod.addHeader(WANT_DIGEST, "sha");
263        checkExternalDataStreamResponseHeader(getObjMethod, null, expectedDigestHeaderValue);
264    }
265
266    @Test
267    public void testProxyWithWantDigestMultipleForLocalFile() throws IOException {
268
269        final File externalFile = createExternalLocalFile(TEST_BINARY_CONTENT);
270
271        final String fileUri = externalFile.toURI().toString();
272
273        final String id = getRandomUniqueId();
274        final HttpPut put = putObjMethod(id);
275        put.addHeader(LINK, getExternalContentLinkHeader(fileUri, "proxy", "text/plain"));
276        assertEquals(CREATED.getStatusCode(), getStatus(put));
277
278        // HEAD request with Want-Digest
279        final HttpHead headObjMethod = headObjMethod(id);
280        headObjMethod.addHeader(WANT_DIGEST, "sha, md5;q=0.3");
281        try (final CloseableHttpResponse response = execute(headObjMethod)) {
282            assertEquals(OK.getStatusCode(), response.getStatusLine().getStatusCode());
283            assertContentLocation(response, fileUri);
284            assertTrue(response.getHeaders(DIGEST).length > 0);
285
286            final String digesterHeaderValue = response.getHeaders(DIGEST)[0].getValue();
287            assertTrue("SHA-1 Fixity Checksum doesn't match",
288                    digesterHeaderValue.contains(TEST_SHA_DIGEST_HEADER_VALUE));
289            assertTrue("MD5 fixity checksum doesn't match",
290                    digesterHeaderValue.contains(TEST_MD5_DIGEST_HEADER_VALUE));
291        }
292
293        // GET request with Want-Digest
294        final HttpGet getObjMethod = getObjMethod(id);
295        getObjMethod.addHeader(WANT_DIGEST, "sha, md5;q=0.3");
296        try (final CloseableHttpResponse response = execute(getObjMethod)) {
297            assertEquals(OK.getStatusCode(), response.getStatusLine().getStatusCode());
298            assertContentLocation(response, fileUri);
299            assertTrue(response.getHeaders(DIGEST).length > 0);
300
301            final String digesterHeaderValue = response.getHeaders(DIGEST)[0].getValue();
302            assertTrue("SHA-1 Fixity Checksum doesn't match",
303                    digesterHeaderValue.contains(TEST_SHA_DIGEST_HEADER_VALUE));
304            assertTrue("MD5 fixity checksum doesn't match",
305                    digesterHeaderValue.contains(TEST_MD5_DIGEST_HEADER_VALUE));
306        }
307    }
308
309    private File createExternalLocalFile(final String content) throws IOException {
310        final File externalFile = tempFolder.newFile();
311        try (final FileWriter fw = new FileWriter(externalFile)) {
312            fw.write(content);
313        }
314        return externalFile;
315    }
316
317    private void checkExternalDataStreamResponseHeader(final HttpUriRequest req, final String contenLocation,
318            final String shaValue) throws IOException {
319        try (final CloseableHttpResponse response = execute(req)) {
320            assertEquals(OK.getStatusCode(), response.getStatusLine().getStatusCode());
321            assertTrue(response.getHeaders(DIGEST).length > 0);
322            if (StringUtils.isNoneBlank(contenLocation)) {
323                assertEquals(contenLocation, getContentLocation(response));
324            }
325            final String digesterHeaderValue = response.getHeaders(DIGEST)[0].getValue();
326            assertTrue("Fixity Checksum doesn't match",
327                    digesterHeaderValue.equals(shaValue));
328        }
329    }
330
331    @Test
332    public void testHeadExternalDatastreamRedirectForHttpUri() throws Exception {
333
334        final String externalLocation = createHttpResource(TEST_BINARY_CONTENT);
335
336        final String id = getRandomUniqueId();
337        final HttpPut put = putObjMethod(id);
338        put.addHeader(LINK, getExternalContentLinkHeader(externalLocation, "redirect", "image/jpeg"));
339        assertEquals(CREATED.getStatusCode(), getStatus(put));
340
341        // Configure HEAD request to NOT follow redirects
342        final HttpHead headObjMethod = headObjMethod(id);
343        final RequestConfig.Builder requestConfig = RequestConfig.custom();
344        requestConfig.setRedirectsEnabled(false);
345        headObjMethod.setConfig(requestConfig.build());
346
347        try (final CloseableHttpResponse response = execute(headObjMethod)) {
348            assertEquals(TEMPORARY_REDIRECT.getStatusCode(), response.getStatusLine().getStatusCode());
349            assertLocation(response, externalLocation);
350            assertEquals("bytes", response.getFirstHeader("Accept-Ranges").getValue());
351            assertContentLength(response, 0);
352            assertContentType(response, "image/jpeg");
353            final ContentDisposition disposition =
354                    new ContentDisposition(response.getFirstHeader(CONTENT_DISPOSITION).getValue());
355            assertEquals("attachment", disposition.getType());
356        }
357    }
358
359    @Test
360    public void testGetExternalDatastreamForHttpUri() throws Exception {
361
362        final String externalLocation = createHttpResource(TEST_BINARY_CONTENT);
363
364        final String id = getRandomUniqueId();
365        final HttpPut put = putObjMethod(id);
366        put.addHeader(LINK, getExternalContentLinkHeader(externalLocation, "redirect", "image/jpeg"));
367        assertEquals(CREATED.getStatusCode(), getStatus(put));
368
369        // Configure HEAD request to NOT follow redirects
370        final HttpGet getObjMethod = getObjMethod(id);
371        final RequestConfig.Builder requestConfig = RequestConfig.custom();
372        requestConfig.setRedirectsEnabled(false);
373        getObjMethod.setConfig(requestConfig.build());
374
375        try (final CloseableHttpResponse response = execute(getObjMethod)) {
376            assertEquals(TEMPORARY_REDIRECT.getStatusCode(), response.getStatusLine().getStatusCode());
377            assertLocation(response, externalLocation);
378            assertContentType(response, "image/jpeg");
379            assertEquals("bytes", response.getFirstHeader("Accept-Ranges").getValue());
380            final ContentDisposition disposition =
381                    new ContentDisposition(response.getFirstHeader(CONTENT_DISPOSITION).getValue());
382            assertEquals("attachment", disposition.getType());
383        }
384    }
385
386    private void checkRedirectWantDigestResult(final HttpRequestBase request, final String dsUrl, final String sha1,
387            final String md5) throws IOException {
388        try (final CloseableHttpResponse response = execute(request)) {
389            assertEquals(TEMPORARY_REDIRECT.getStatusCode(), response.getStatusLine().getStatusCode());
390            assertLocation(response, dsUrl);
391            assertTrue(response.getHeaders(DIGEST).length > 0);
392
393            final String digesterHeaderValue = response.getHeaders(DIGEST)[0].getValue();
394            assertTrue("SHA-1 Fixity Checksum doesn't match",
395                    digesterHeaderValue.contains(sha1));
396            if (md5 != null) {
397                assertTrue("MD5 fixity checksum doesn't match",
398                        digesterHeaderValue.contains(md5));
399            }
400        }
401    }
402
403    @Test
404    public void testRedirectWithWantDigest() throws Exception {
405
406        final String dsUrl = createHttpResource(TEST_BINARY_CONTENT);
407
408        final String id = getRandomUniqueId();
409        final HttpPut put = putObjMethod(id);
410        put.addHeader(LINK, getExternalContentLinkHeader(dsUrl, "redirect", "image/jpeg"));
411        assertEquals(CREATED.getStatusCode(), getStatus(put));
412
413        // Configure request to NOT follow redirects
414        final RequestConfig.Builder requestConfig = RequestConfig.custom();
415        requestConfig.setRedirectsEnabled(false);
416
417        // Verify HEAD request behavior with single Want-Digest
418        final HttpHead headObjMethod = headObjMethod(id);
419        headObjMethod.addHeader(WANT_DIGEST, "sha");
420        headObjMethod.setConfig(requestConfig.build());
421
422        checkRedirectWantDigestResult(headObjMethod, dsUrl,
423                TEST_SHA_DIGEST_HEADER_VALUE, null);
424
425        // Verify HEAD request behavior with multiple Want-Digest
426        final HttpHead headObjMethodMulti = headObjMethod(id);
427        headObjMethodMulti.addHeader(WANT_DIGEST, "sha, md5;q=0.3");
428        headObjMethodMulti.setConfig(requestConfig.build());
429
430        checkRedirectWantDigestResult(headObjMethodMulti, dsUrl,
431                TEST_SHA_DIGEST_HEADER_VALUE, TEST_MD5_DIGEST_HEADER_VALUE);
432
433        // Verify GET request behavior with Want-Digest
434        final HttpGet getObjMethod = getObjMethod(id);
435        getObjMethod.addHeader(WANT_DIGEST, "sha");
436        getObjMethod.setConfig(requestConfig.build());
437
438        checkRedirectWantDigestResult(getObjMethod, dsUrl,
439                TEST_SHA_DIGEST_HEADER_VALUE, null);
440
441        // Verify GET with multiple Want-Digest
442        final HttpGet getObjMethodMulti = getObjMethod(id);
443        getObjMethodMulti.addHeader(WANT_DIGEST, "sha, md5;q=0.3");
444        getObjMethodMulti.setConfig(requestConfig.build());
445
446        checkRedirectWantDigestResult(getObjMethodMulti, dsUrl,
447                TEST_SHA_DIGEST_HEADER_VALUE, TEST_MD5_DIGEST_HEADER_VALUE);
448    }
449
450    @Test
451    public void testRedirectForHttpUri() throws Exception {
452
453        final String externalLocation = createHttpResource(TEST_BINARY_CONTENT);
454
455        final String id = getRandomUniqueId();
456        final HttpPut httpPut = putObjMethod(id);
457        httpPut.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
458        httpPut.addHeader(LINK, getExternalContentLinkHeader(externalLocation, "redirect", null));
459
460        try (final CloseableHttpResponse response = execute(httpPut)) {
461            assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response));
462            final HttpGet get = new HttpGet(getLocation(response));
463            try (final CloseableHttpResponse getResponse = noFollowClient.execute(get)) {
464                assertEquals(TEMPORARY_REDIRECT.getStatusCode(), getStatus(getResponse));
465                assertLocation(getResponse, externalLocation);
466            }
467        }
468    }
469
470    @Test
471    public void testProxyLocalFile() throws Exception {
472        final File localFile = createExternalLocalFile(TEST_BINARY_CONTENT);
473
474        final String id = getRandomUniqueId();
475        final HttpPut httpPut = putObjMethod(id);
476        httpPut.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
477        final String fileUri = localFile.toURI().toString();
478        httpPut.addHeader(LINK, getExternalContentLinkHeader(fileUri, "proxy", "text/plain"));
479
480        try (final CloseableHttpResponse response = execute(httpPut)) {
481            assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response));
482            final HttpGet get = new HttpGet(getLocation(response));
483            try (final CloseableHttpResponse getResponse = execute(get)) {
484                assertEquals(OK.getStatusCode(), getStatus(getResponse));
485                assertContentLocation(getResponse, fileUri);
486                assertContentLength(getResponse, TEST_BINARY_CONTENT.length());
487                assertBodyMatches(getResponse, TEST_BINARY_CONTENT);
488            }
489        }
490    }
491
492    @Test
493    public void testCopyLocalFile() throws Exception {
494        final String entityStr = "Hello there, this is the original object speaking.";
495
496        final File localFile = createExternalLocalFile(entityStr);
497
498        final String id = getRandomUniqueId();
499        final HttpPut httpPut = putObjMethod(id);
500        httpPut.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
501        final String localPath = localFile.toURI().toString();
502        httpPut.addHeader(LINK, getExternalContentLinkHeader(localPath, "copy", "text/plain"));
503
504        try (final CloseableHttpResponse response = execute(httpPut)) {
505            assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response));
506
507            // fetch the copy of the object
508            final HttpGet get = new HttpGet(getLocation(response));
509            try (final CloseableHttpResponse getResponse = execute(get)) {
510                assertEquals(OK.getStatusCode(), getStatus(getResponse));
511                assertContentType(getResponse, "text/plain");
512                assertBodyMatches(getResponse, entityStr);
513            }
514        }
515    }
516
517    @Test
518    public void testCopyForHttpUri() throws Exception {
519        // create a random binary object
520        final var entityStr = "Hello there, this is the original object speaking.";
521        final String copyLocation = createHttpResource(entityStr);
522
523        // create a copy of it
524        final String id = getRandomUniqueId();
525        final HttpPut httpPut = putObjMethod(id);
526        httpPut.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
527        httpPut.addHeader(LINK, getExternalContentLinkHeader(copyLocation, "copy", "text/plain"));
528
529        try (final CloseableHttpResponse response = execute(httpPut)) {
530            assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response));
531
532            // fetch the copy of the object
533            final HttpGet get = new HttpGet(getLocation(response));
534            try (final CloseableHttpResponse getResponse = execute(get)) {
535                assertEquals(OK.getStatusCode(), getStatus(getResponse));
536                assertContentType(getResponse, "text/plain");
537                assertBodyMatches(getResponse, entityStr);
538            }
539        }
540    }
541
542    @Test
543    public void testProxyForHttpUri() throws Exception {
544        // Create a resource
545        final String entityStr = "Hello there, this is the original object speaking.";
546        final String origLocation = createHttpResource(entityStr);
547
548        final String id = getRandomUniqueId();
549        final HttpPut httpPut = putObjMethod(id);
550        httpPut.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
551        httpPut.addHeader(LINK, getExternalContentLinkHeader(origLocation, "proxy", null));
552
553        try (final CloseableHttpResponse response = execute(httpPut)) {
554            assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response));
555            final HttpGet get = new HttpGet(getLocation(response));
556            try (final CloseableHttpResponse getResponse = execute(get)) {
557                assertEquals(OK.getStatusCode(), getStatus(getResponse));
558                assertContentLocation(getResponse, origLocation);
559                assertBodyMatches(getResponse, entityStr);
560            }
561        }
562    }
563
564    @Test
565    public void testPostExternalContentProxyForHttpUri() throws Exception {
566        // Create a resource
567        final String entityStr = "Hello there, this is the original object speaking.";
568        final String origLocation = createHttpResource(entityStr);
569
570        final String id = getRandomUniqueId();
571        final HttpPost httpPost = postObjMethod();
572        httpPost.addHeader("Slug", id);
573        httpPost.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
574        httpPost.addHeader(LINK, getExternalContentLinkHeader(origLocation, "proxy", "text/plain"));
575
576        try (final CloseableHttpResponse response = execute(httpPost)) {
577            assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response));
578            final HttpGet get = new HttpGet(getLocation(response));
579            try (final CloseableHttpResponse getResponse = execute(get)) {
580                assertEquals(OK.getStatusCode(), getStatus(getResponse));
581                assertContentLocation(getResponse, origLocation);
582                assertContentType(getResponse, "text/plain");
583                assertBodyMatches(getResponse, entityStr);
584            }
585        }
586    }
587
588    @Test
589    public void testUnsupportedHandlingTypeInExternalMessagePUT() throws IOException {
590
591        final String id = getRandomUniqueId();
592        final HttpPut httpPut = putObjMethod(id);
593        httpPut.addHeader(LINK, getExternalContentLinkHeader("http://example.com/test", "junk", "image/jpeg"));
594
595        try (final CloseableHttpResponse response = execute(httpPut)) {
596            assertEquals("Didn't get a BAD REQUEST error!", BAD_REQUEST.getStatusCode(),
597                    getStatus(response));
598            assertBodyContains(response, "External content link header url is malformed");
599        }
600    }
601
602    @Test
603    public void testUnsupportedHandlingTypeInExternalMessagePOST() throws IOException {
604        final HttpPost httpPost = postObjMethod();
605        httpPost.addHeader(LINK, getExternalContentLinkHeader("http://example.com/junk", "junk", "image/jpeg"));
606
607        try (final CloseableHttpResponse response = execute(httpPost)) {
608            assertEquals("Didn't get a BAD_REQUEST response!", BAD_REQUEST.getStatusCode(),
609                    getStatus(response));
610            assertBodyContains(response, "External content link header url is malformed");
611        }
612    }
613
614    @Test
615    public void testMissingHandlingTypeInExternalMessage() throws IOException {
616        final String id = getRandomUniqueId();
617        final HttpPut httpPut = putObjMethod(id);
618        httpPut.addHeader(LINK, getExternalContentLinkHeader("http://example.com/junk", null, "image/jpeg"));
619
620        try (final CloseableHttpResponse response = execute(httpPut)) {
621            assertEquals("Didn't get a BAD_REQUEST response!", BAD_REQUEST.getStatusCode(),
622                    getStatus(response));
623            assertBodyContains(response, "External content link header url is malformed");
624        }
625    }
626
627    @Test
628    public void testCopyNotFoundHttpContent() throws Exception {
629        final String nonexistentPath = serverAddress + getRandomUniqueId();
630
631        // create a copy of it
632        final HttpPost httpPost = postObjMethod();
633        httpPost.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
634        httpPost.addHeader(LINK, getExternalContentLinkHeader(nonexistentPath, "copy", null));
635
636        try (final CloseableHttpResponse response = execute(httpPost)) {
637            assertEquals("Didn't get a BAD_REQUEST response!", BAD_REQUEST.getStatusCode(),
638                    getStatus(response));
639            assertBodyContains(response, "Unable to access external binary");
640        }
641    }
642
643    @Test
644    public void testCopyUnreachableHttpContent() throws Exception {
645        final String nonexistentPath = "http://" + getRandomUniqueId() + ".example.com/";
646
647        // create a copy of it
648        final HttpPost httpPost = postObjMethod();
649        httpPost.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
650        httpPost.addHeader(LINK, getExternalContentLinkHeader(nonexistentPath, "copy", null));
651
652        try (final CloseableHttpResponse response = execute(httpPost)) {
653            assertEquals("Didn't get a BAD_REQUEST response!", BAD_REQUEST.getStatusCode(),
654                    getStatus(response));
655            assertBodyContains(response, "Unable to access external binary");
656        }
657    }
658
659    @Test
660    public void testProxyNotFoundHttpContent() throws Exception {
661        final String nonexistentPath = serverAddress + getRandomUniqueId();
662
663        // create a copy of it
664        final HttpPost httpPost = postObjMethod();
665        httpPost.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
666        httpPost.addHeader(LINK, getExternalContentLinkHeader(nonexistentPath, "proxy", null));
667
668        try (final CloseableHttpResponse response = execute(httpPost)) {
669            assertEquals("Expected failure on creation", BAD_REQUEST.getStatusCode(),
670                    getStatus(response));
671            assertBodyContains(response, "Unable to access external binary");
672        }
673    }
674
675    @Test
676    public void testProxyUnreachableHttpContent() throws Exception {
677        final String nonexistentPath = "http://" + getRandomUniqueId() + ".example.com/";
678
679        // create a copy of it
680        final HttpPost httpPost = postObjMethod();
681        httpPost.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
682        httpPost.addHeader(LINK, getExternalContentLinkHeader(nonexistentPath, "proxy", null));
683
684        try (final CloseableHttpResponse response = execute(httpPost)) {
685            assertEquals("Expected failure on creation", BAD_REQUEST.getStatusCode(),
686                    getStatus(response));
687            assertBodyContains(response, "Unable to access external binary");
688        }
689    }
690
691    @Test
692    public void testRedirectUnreachableHttpContent() throws Exception {
693        final String nonexistentPath = "http://" + getRandomUniqueId() + ".example.com/";
694
695        // create a copy of it
696        final HttpPost httpPost = postObjMethod();
697        httpPost.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
698        httpPost.addHeader(LINK, getExternalContentLinkHeader(nonexistentPath, "redirect", null));
699
700        try (final CloseableHttpResponse response = execute(httpPost)) {
701            assertEquals("Expected failure on creation", BAD_REQUEST.getStatusCode(),
702                    getStatus(response));
703            assertBodyContains(response, "Unable to access external binary");
704        }
705    }
706
707    @Test
708    public void testProxyNotFoundLocalFile() throws Exception {
709        verifyNotFoundLocalFile("proxy");
710    }
711
712    @Test
713    public void testRedirectNotFoundLocalFile() throws Exception {
714        verifyNotFoundLocalFile("redirect");
715    }
716
717    @Test
718    public void testCopyNotFoundLocalFile() throws Exception {
719        verifyNotFoundLocalFile("copy");
720    }
721
722    private void verifyNotFoundLocalFile(final String handling) throws Exception {
723        final File nonexistentFile = tempFolder.newFile();
724        nonexistentFile.delete();
725        final String nonexistentUri = nonexistentFile.toURI().toString();
726
727        // create a copy of it
728        final HttpPost httpPost = postObjMethod();
729        httpPost.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
730        httpPost.addHeader(LINK, getExternalContentLinkHeader(nonexistentUri, handling, null));
731
732        try (final CloseableHttpResponse response = execute(httpPost)) {
733            assertEquals("Expected failure on creation", BAD_REQUEST.getStatusCode(),
734                    getStatus(response));
735            assertBodyContains(response, "Path did not match any allowed external content paths");
736        }
737    }
738
739    @Test
740    public void testCopyWithTransmissionFixityForLocalFile() throws Exception {
741        final File localFile = createExternalLocalFile(TEST_BINARY_CONTENT);
742
743        final String localPath = localFile.toURI().toString();
744        final HttpPut httpPut = setupExternalContentPut(localPath, "copy", "text/plain");
745        httpPut.addHeader("Digest", TEST_SHA_DIGEST_HEADER_VALUE);
746
747        try (final CloseableHttpResponse response = execute(httpPut)) {
748            assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response));
749
750            // fetch the copy of the object
751            final HttpGet get = new HttpGet(getLocation(response));
752            try (final CloseableHttpResponse getResponse = execute(get)) {
753                assertEquals(OK.getStatusCode(), getStatus(getResponse));
754                assertContentType(getResponse, "text/plain");
755                assertBodyMatches(getResponse, TEST_BINARY_CONTENT);
756            }
757        }
758    }
759
760    @Test
761    public void testCopyWithInvalidTransmissionFixityForLocalFile() throws Exception {
762        final String content = "Not the expected content";
763        final File localFile = createExternalLocalFile(content);
764        final String localPath = localFile.toURI().toString();
765
766        final HttpPut httpPut = setupExternalContentPut(localPath, "copy", "text/plain");
767        httpPut.addHeader("Digest", TEST_SHA_DIGEST_HEADER_VALUE);
768
769        try (final CloseableHttpResponse response = execute(httpPut)) {
770            assertEquals(CONFLICT.getStatusCode(), getStatus(response));
771        }
772    }
773
774    @Test
775    public void testCopyWithTransmissionFixityForHttpUri() throws Exception {
776        final String externalLocation = createHttpResource("text/plain", TEST_BINARY_CONTENT);
777        final HttpPut httpPut = setupExternalContentPut(externalLocation, "copy", "text/plain");
778        httpPut.addHeader("Digest", TEST_SHA_DIGEST_HEADER_VALUE);
779
780        try (final CloseableHttpResponse response = execute(httpPut)) {
781            assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response));
782
783            // fetch the copy of the object
784            final HttpGet get = new HttpGet(getLocation(response));
785            try (final CloseableHttpResponse getResponse = execute(get)) {
786                assertEquals(OK.getStatusCode(), getStatus(getResponse));
787                assertContentType(getResponse, "text/plain");
788                assertBodyMatches(getResponse, TEST_BINARY_CONTENT);
789            }
790        }
791    }
792
793    @Test
794    public void testProxyWithTransmissionFixityForLocalFile() throws Exception {
795        final File localFile = createExternalLocalFile(TEST_BINARY_CONTENT);
796
797        final String externalLocation = localFile.toURI().toString();
798        final HttpPut httpPut = setupExternalContentPut(externalLocation, "proxy", "text/plain");
799        httpPut.addHeader("Digest", TEST_SHA_DIGEST_HEADER_VALUE);
800
801        try (final CloseableHttpResponse response = execute(httpPut)) {
802            assertEquals("Didn't get a CREATED response!", CREATED.getStatusCode(), getStatus(response));
803            assertIsProxyBinary(getLocation(response), externalLocation, TEST_BINARY_CONTENT, "text/plain");
804        }
805    }
806
807    @Test
808    public void testProxyWithInvalidTransmissionFixityForLocalFile() throws Exception {
809        final File localFile = createExternalLocalFile(TEST_BINARY_CONTENT);
810
811        final String externalLocation = localFile.toURI().toString();
812        final HttpPut httpPut = setupExternalContentPut(externalLocation, "proxy", "text/plain");
813        httpPut.addHeader("Digest", "sha=12345678910");
814
815        try (final CloseableHttpResponse response = execute(httpPut)) {
816            assertEquals(CONFLICT.getStatusCode(), getStatus(response));
817        }
818    }
819
820    @Test
821    public void testRedirectWithTransmissionFixityForHttpUri() throws Exception {
822        final String externalLocation = createHttpResource("text/plain", TEST_BINARY_CONTENT);
823        final HttpPut httpPut = setupExternalContentPut(externalLocation, "redirect", "text/plain");
824        httpPut.addHeader("Digest", TEST_SHA_DIGEST_HEADER_VALUE);
825
826        try (final CloseableHttpResponse response = execute(httpPut)) {
827            assertEquals(CREATED.getStatusCode(), getStatus(response));
828            assertIsRedirectBinary(getLocation(response), externalLocation, TEST_BINARY_CONTENT, "text/plain");
829        }
830    }
831
832    @Test
833    public void testRedirectWithInvalidTransmissionFixityForHttpUri() throws Exception {
834        final String externalLocation = createHttpResource("text/plain", "bad content");
835        final HttpPut httpPut = setupExternalContentPut(externalLocation, "redirect", "text/plain");
836        httpPut.addHeader("Digest", TEST_SHA_DIGEST_HEADER_VALUE);
837
838        try (final CloseableHttpResponse response = execute(httpPut)) {
839            assertEquals(CONFLICT.getStatusCode(), getStatus(response));
840        }
841    }
842
843    @Test
844    public void testProxyPostWithTransmissionFixityForHttpUri() throws Exception {
845        final String externalLocation = createHttpResource("text/plain", TEST_BINARY_CONTENT);
846        final HttpPost httpPost = postObjMethod();
847        httpPost.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
848        httpPost.addHeader(LINK, getExternalContentLinkHeader(externalLocation, "proxy", "text/plain"));
849        httpPost.addHeader("Digest", TEST_SHA_DIGEST_HEADER_VALUE);
850
851        try (final CloseableHttpResponse response = execute(httpPost)) {
852            assertEquals(CREATED.getStatusCode(), getStatus(response));
853            assertIsProxyBinary(getLocation(response), externalLocation, TEST_BINARY_CONTENT, "text/plain");
854        }
855    }
856
857    @Test
858    public void testProxyPostWithInvalidTransmissionFixityForHttpUri() throws Exception {
859        final String externalLocation = createHttpResource("text/plain", "bad content");
860        final HttpPost httpPost = postObjMethod();
861        httpPost.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
862        httpPost.addHeader(LINK, getExternalContentLinkHeader(externalLocation, "proxy", "text/plain"));
863        httpPost.addHeader("Digest", TEST_SHA_DIGEST_HEADER_VALUE);
864
865        try (final CloseableHttpResponse response = execute(httpPost)) {
866            assertEquals(CONFLICT.getStatusCode(), getStatus(response));
867        }
868    }
869
870    @Test
871    public void testRedirectPostWithTransmissionFixityForHttpUri() throws Exception {
872        final String externalLocation = createHttpResource("text/plain", TEST_BINARY_CONTENT);
873        final HttpPost httpPost = postObjMethod();
874        httpPost.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
875        httpPost.addHeader(LINK, getExternalContentLinkHeader(externalLocation, "redirect", "text/plain"));
876        httpPost.addHeader("Digest", TEST_SHA_DIGEST_HEADER_VALUE);
877
878        try (final CloseableHttpResponse response = execute(httpPost)) {
879            assertEquals(CREATED.getStatusCode(), getStatus(response));
880            assertIsRedirectBinary(getLocation(response), externalLocation, TEST_BINARY_CONTENT, "text/plain");
881        }
882    }
883
884    @Test
885    public void testRedirectPostWithInvalidTransmissionFixityForHttpUri() throws Exception {
886        final String externalLocation = createHttpResource("text/plain", "bad content");
887        final HttpPost httpPost = postObjMethod();
888        httpPost.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
889        httpPost.addHeader(LINK, getExternalContentLinkHeader(externalLocation, "redirect", "text/plain"));
890        httpPost.addHeader("Digest", TEST_SHA_DIGEST_HEADER_VALUE);
891
892        try (final CloseableHttpResponse response = execute(httpPost)) {
893            assertEquals(CONFLICT.getStatusCode(), getStatus(response));
894        }
895    }
896
897    @Test
898    public void testProxyPutWithTransmissionFixityForHttpUri() throws Exception {
899        final String externalLocation = createHttpResource("text/plain", TEST_BINARY_CONTENT);
900
901        final HttpPut httpPut = setupExternalContentPut(externalLocation, "proxy", "text/plain");
902        httpPut.addHeader("Digest", TEST_SHA_DIGEST_HEADER_VALUE);
903
904        try (final CloseableHttpResponse response = execute(httpPut)) {
905            assertEquals(CREATED.getStatusCode(), getStatus(response));
906            assertIsProxyBinary(getLocation(response), externalLocation, TEST_BINARY_CONTENT, "text/plain");
907        }
908    }
909
910    @Test
911    public void testUpdateProxyHttpUri() throws Exception {
912        final String content2 = "<doc>some more content</doc>";
913        final String externalLocation1 = createHttpResource("text/plain", TEST_BINARY_CONTENT);
914        final String externalLocation2 = createHttpResource("text/xml", content2);
915
916        final String rescLoc = createExternalContentResource(externalLocation1, "proxy", null);
917        assertIsProxyBinary(rescLoc, externalLocation1, TEST_BINARY_CONTENT, "text/plain");
918
919        updateExternalContentResource(rescLoc, externalLocation2, "proxy", null);
920        assertIsProxyBinary(rescLoc, externalLocation2, content2, "text/xml");
921    }
922
923    @Test
924    public void testUpdateProxyLocalFile() throws Exception {
925        final String content2 = "<doc>some more content</doc>";
926        final File localFile1 = createExternalLocalFile(TEST_BINARY_CONTENT);
927        final File localFile2 = createExternalLocalFile(content2);
928        final String externalLocation1 = localFile1.toURI().toString();
929        final String externalLocation2 = localFile2.toURI().toString();
930
931        final String rescLoc = createExternalContentResource(externalLocation1, "proxy", null);
932        assertIsProxyBinary(rescLoc, externalLocation1, TEST_BINARY_CONTENT, "application/octet-stream");
933
934        updateExternalContentResource(rescLoc, externalLocation2, "proxy", "text/xml");
935        assertIsProxyBinary(rescLoc, externalLocation2, content2, "text/xml");
936    }
937
938    @Test
939    public void testUpdateRedirectHttpUri() throws Exception {
940        final String content2 = "<doc>some more content</doc>";
941        final String externalLocation1 = createHttpResource("text/plain", TEST_BINARY_CONTENT);
942        final String externalLocation2 = createHttpResource(content2);
943
944        final String rescLoc = createExternalContentResource(externalLocation1, "redirect", null);
945        assertIsRedirectBinary(rescLoc, externalLocation1, TEST_BINARY_CONTENT, "text/plain");
946
947        updateExternalContentResource(rescLoc, externalLocation2, "redirect", "text/xml");
948        assertIsRedirectBinary(rescLoc, externalLocation2, content2, "text/xml");
949    }
950
951    @Test
952    public void testUpdateProxyToRedirectForHttpUri() throws Exception {
953        // Create a resource
954        final String externalLocation = createHttpResource(TEST_BINARY_CONTENT);
955
956        final String rescLoc = createExternalContentResource(externalLocation, "proxy", null);
957        assertIsProxyBinary(rescLoc, externalLocation, TEST_BINARY_CONTENT, null);
958
959        updateExternalContentResource(rescLoc, externalLocation, "redirect", null);
960        assertIsRedirectBinary(rescLoc, externalLocation, TEST_BINARY_CONTENT, null);
961    }
962
963    @Test
964    public void testUpdateRedirectToProxyForHttpUri() throws Exception {
965        final String externalLocation = createHttpResource(TEST_BINARY_CONTENT);
966
967        final String rescLoc = createExternalContentResource(externalLocation, "redirect", null);
968        assertIsRedirectBinary(rescLoc, externalLocation, TEST_BINARY_CONTENT, null);
969
970        updateExternalContentResource(rescLoc, externalLocation, "proxy", null);
971        assertIsProxyBinary(rescLoc, externalLocation, TEST_BINARY_CONTENT, null);
972    }
973
974    @Test
975    public void testUpdateProxyToRedirectForLocalFile() throws Exception {
976        final String content2 = "<doc>some more content</doc>";
977        final File localFile1 = createExternalLocalFile(TEST_BINARY_CONTENT);
978        final File localFile2 = createExternalLocalFile(content2);
979        final String externalLocation1 = localFile1.toURI().toString();
980        final String externalLocation2 = localFile2.toURI().toString();
981
982        final String rescLoc = createExternalContentResource(externalLocation1, "proxy", "text/plain");
983        assertIsProxyBinary(rescLoc, externalLocation1, TEST_BINARY_CONTENT, "text/plain");
984
985        updateExternalContentResource(rescLoc, externalLocation2, "redirect", "text/xml");
986        // Not checking on the content, since following a redirect on a file is unlikely to work
987        assertIsRedirectBinary(rescLoc, externalLocation2, null, "text/xml");
988    }
989
990    @Test
991    public void testUpdateHttpUriToLocalFile() throws Exception {
992        final String content2 = "some more content";
993        final String externalLocation1 = createHttpResource(TEST_BINARY_CONTENT);
994        final File localFile2 = createExternalLocalFile(content2);
995        final String externalLocation2 = localFile2.toURI().toString();
996
997        final String rescLoc = createExternalContentResource(externalLocation1, "proxy", null);
998        assertIsProxyBinary(rescLoc, externalLocation1, TEST_BINARY_CONTENT, "text/plain");
999
1000        updateExternalContentResource(rescLoc, externalLocation2, "proxy", null);
1001        assertIsProxyBinary(rescLoc, externalLocation2, content2, "application/octet-stream");
1002    }
1003
1004    @Test
1005    public void testUpdateInternalToLocalFile() throws Exception {
1006        final String id = getRandomUniqueId();
1007        final String rescLoc;
1008        try (final CloseableHttpResponse response = execute(putDSMethod(id, "x", TEST_BINARY_CONTENT))) {
1009            assertEquals(CREATED.getStatusCode(), getStatus(response));
1010            rescLoc = getLocation(response);
1011        }
1012
1013        final String content2 = "<doc>some more content</doc>";
1014        final File localFile = createExternalLocalFile(content2);
1015        final String externalLocation = localFile.toURI().toString();
1016
1017        updateExternalContentResource(rescLoc, externalLocation, "proxy", "text/xml");
1018        assertIsProxyBinary(rescLoc, externalLocation, content2, "text/xml");
1019    }
1020
1021    @Test
1022    public void testUpdateLocalFileToInternal() throws Exception {
1023        final File localFile = createExternalLocalFile(TEST_BINARY_CONTENT);
1024        final String externalLocation = localFile.toURI().toString();
1025
1026        final String rescLoc = createExternalContentResource(externalLocation, "proxy", "text/plain");
1027        assertIsProxyBinary(rescLoc, externalLocation, TEST_BINARY_CONTENT, "text/plain");
1028
1029        final String content2 = "<doc>some more content</doc>";
1030        final HttpPut put = new HttpPut(rescLoc);
1031        put.setEntity(new StringEntity(content2));
1032        put.setHeader(CONTENT_TYPE, "text/xml");
1033        assertEquals(NO_CONTENT.getStatusCode(), getStatus(put));
1034
1035        final HttpGet get = new HttpGet(rescLoc);
1036        try (final CloseableHttpResponse resp = execute(get)) {
1037            assertEquals(OK.getStatusCode(), getStatus(resp));
1038            assertNull(resp.getFirstHeader("Content-Location"));
1039            assertContentLength(resp, content2.length());
1040            assertBodyMatches(resp, content2);
1041            assertContentType(resp, "text/xml");
1042        }
1043    }
1044
1045    @Test
1046    public void testUpdateInternalToHttpUri() throws Exception {
1047        final String id = getRandomUniqueId();
1048        final String rescLoc;
1049        try (final CloseableHttpResponse response = execute(putDSMethod(id, "x", TEST_BINARY_CONTENT))) {
1050            assertEquals(CREATED.getStatusCode(), getStatus(response));
1051            rescLoc = getLocation(response);
1052        }
1053
1054        final String content2 = "<doc>some more content</doc>";
1055        final String externalLocation = createHttpResource(content2);
1056
1057        updateExternalContentResource(rescLoc, externalLocation, "redirect", "text/xml");
1058        assertIsRedirectBinary(rescLoc, externalLocation, content2, "text/xml");
1059    }
1060
1061    @Test
1062    public void testUpdateHttpUriToInternal() throws Exception {
1063        final String externalLocation = createHttpResource(TEST_BINARY_CONTENT);
1064
1065        final String rescLoc = createExternalContentResource(externalLocation, "proxy", null);
1066        assertIsProxyBinary(rescLoc, externalLocation, TEST_BINARY_CONTENT, "text/plain");
1067
1068        final String content2 = "<doc>some more content</doc>";
1069        final HttpPut put = new HttpPut(rescLoc);
1070        put.setEntity(new StringEntity(content2));
1071        put.setHeader(CONTENT_TYPE, "text/xml");
1072        assertEquals(NO_CONTENT.getStatusCode(), getStatus(put));
1073
1074        final HttpGet get = new HttpGet(rescLoc);
1075        try (final CloseableHttpResponse resp = execute(get)) {
1076            assertEquals(OK.getStatusCode(), getStatus(resp));
1077            assertNull(resp.getFirstHeader("Content-Location"));
1078            assertContentLength(resp, content2.length());
1079            assertBodyMatches(resp, content2);
1080            assertContentType(resp, "text/xml");
1081        }
1082    }
1083
1084    @Test
1085    public void testLocalFileNotDeleted() throws Exception {
1086        final File localFile = createExternalLocalFile(TEST_BINARY_CONTENT);
1087        final String externalLocation = localFile.toURI().toString();
1088
1089        final String rescLoc = createExternalContentResource(externalLocation, "proxy", "text/plain");
1090        final String id = StringUtils.substringAfterLast(rescLoc, "/");
1091        assertIsProxyBinary(rescLoc, externalLocation, TEST_BINARY_CONTENT, "text/plain");
1092
1093        final HttpDelete delete = new HttpDelete(rescLoc);
1094        assertEquals(NO_CONTENT.getStatusCode(), getStatus(delete));
1095        assertDeleted(id);
1096        assertTrue("External binary must exist after resource deletion", localFile.exists());
1097
1098        final HttpDelete deleteTomb = new HttpDelete(rescLoc + "/" + FedoraTypes.FCR_TOMBSTONE);
1099        assertEquals(NO_CONTENT.getStatusCode(), getStatus(deleteTomb));
1100        assertTrue("External binary must exist after deleting tombstone", localFile.exists());
1101    }
1102
1103    private HttpPut setupExternalContentPut(final String externalLocation, final String handling,
1104            final String contentType) {
1105        final String id = getRandomUniqueId();
1106        final HttpPut httpPut = putObjMethod(id);
1107        httpPut.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
1108        httpPut.addHeader(LINK, getExternalContentLinkHeader(externalLocation, handling, contentType));
1109        return httpPut;
1110    }
1111
1112    private String createExternalContentResource(final String externalLocation, final String handling,
1113            final String contentType) throws IOException {
1114        final HttpPut httpPut = setupExternalContentPut(externalLocation, handling, contentType);
1115
1116        try (final CloseableHttpResponse response = execute(httpPut)) {
1117            assertEquals(CREATED.getStatusCode(), getStatus(response));
1118
1119            return getLocation(response);
1120        }
1121    }
1122
1123    private void updateExternalContentResource(final String rescLoc, final String externalLocation,
1124            final String handling, final String contentType) throws IOException {
1125        final HttpPut httpPut = new HttpPut(rescLoc);
1126        httpPut.addHeader(LINK, getExternalContentLinkHeader(externalLocation, handling, contentType));
1127        try (final CloseableHttpResponse response = execute(httpPut)) {
1128            assertEquals(NO_CONTENT.getStatusCode(), getStatus(response));
1129        }
1130    }
1131
1132    private void assertIsProxyBinary(final String rescLocation, final String expectedLocation,
1133            final String expectedContent, final String expectedType) throws IOException {
1134        final HttpGet get = new HttpGet(rescLocation);
1135        try (final CloseableHttpResponse resp = execute(get)) {
1136            assertEquals(OK.getStatusCode(), getStatus(resp));
1137            assertContentLocation(resp, expectedLocation);
1138            assertContentLength(resp, expectedContent.length());
1139            assertBodyMatches(resp, expectedContent);
1140            if (expectedType != null) {
1141                assertContentType(resp, expectedType);
1142            }
1143        }
1144    }
1145
1146    private void assertIsRedirectBinary(final String rescLocation, final String expectedLocation,
1147            final String expectedContent, final String expectedType) throws IOException {
1148        final HttpGet get = new HttpGet(rescLocation);
1149        try (final CloseableHttpResponse resp = noFollowClient.execute(get)) {
1150            assertEquals(TEMPORARY_REDIRECT.getStatusCode(), getStatus(resp));
1151            assertLocation(resp, expectedLocation);
1152            if (expectedType != null) {
1153                assertContentType(resp, expectedType);
1154            }
1155        }
1156
1157        if (expectedContent != null) {
1158            // Follow redirect to the content
1159            try (final CloseableHttpResponse resp = execute(get)) {
1160                assertBodyMatches(resp, expectedContent);
1161                assertContentLength(resp, expectedContent.length());
1162            }
1163        }
1164    }
1165
1166    private String createHttpResource(final String content) throws Exception {
1167        return createHttpResource("text/plain", content);
1168    }
1169
1170    private String createHttpResource(final String contentType, final String content) throws Exception {
1171        final HttpPost method = postObjMethod();
1172        method.addHeader(CONTENT_TYPE, contentType);
1173        method.addHeader(LINK, NON_RDF_SOURCE_LINK_HEADER);
1174        method.setEntity(new StringEntity(content));
1175
1176        // Make an external remote URI.
1177        try (final CloseableHttpResponse response = execute(method)) {
1178            assertEquals(SC_CREATED, getStatus(response));
1179            return getLocation(response);
1180        }
1181    }
1182
1183    private void assertBodyContains(final CloseableHttpResponse response, final String expected) throws IOException {
1184        final String body = IOUtils.toString(response.getEntity().getContent(), UTF_8);
1185        assertTrue("Expected response to contain '" + expected + "' but was '" + body + "'",
1186                body.contains(expected));
1187    }
1188
1189    private void assertBodyMatches(final CloseableHttpResponse response, final String expected) throws IOException {
1190        final String body = IOUtils.toString(response.getEntity().getContent(), UTF_8);
1191        assertEquals("Response body did not match the expected value", expected, body);
1192    }
1193
1194    private void assertContentLength(final CloseableHttpResponse response, final long expectedLength) {
1195        assertEquals("Content-length header did not match", expectedLength, Long.parseLong(response
1196                .getFirstHeader(CONTENT_LENGTH).getValue()));
1197    }
1198
1199    private void assertContentType(final CloseableHttpResponse response, final String expected) {
1200        assertEquals("Content-type header did not match", expected, response.getFirstHeader(CONTENT_TYPE).getValue());
1201    }
1202
1203    private void assertContentLocation(final CloseableHttpResponse response, final String expectedLoc) {
1204        assertEquals("Content location header did not match", expectedLoc, getContentLocation(response));
1205    }
1206
1207    private void assertLocation(final CloseableHttpResponse response, final String expectedLoc) {
1208        assertEquals("Location header did not match", expectedLoc, getLocation(response));
1209    }
1210}