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.kernel.impl.services;
019
020import static java.util.Arrays.asList;
021import static java.util.Collections.singleton;
022import static org.fcrepo.kernel.api.RdfLexicon.BASIC_CONTAINER;
023import static org.fcrepo.kernel.api.RdfLexicon.CONTAINER;
024import static org.fcrepo.kernel.api.RdfLexicon.CREATED_BY;
025import static org.fcrepo.kernel.api.RdfLexicon.CREATED_DATE;
026import static org.fcrepo.kernel.api.RdfLexicon.DEFAULT_INTERACTION_MODEL;
027import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_RESOURCE;
028import static org.fcrepo.kernel.api.RdfLexicon.LAST_MODIFIED_BY;
029import static org.fcrepo.kernel.api.RdfLexicon.LAST_MODIFIED_DATE;
030import static org.fcrepo.kernel.api.RdfLexicon.MEMENTO_TYPE;
031import static org.fcrepo.kernel.api.RdfLexicon.NON_RDF_SOURCE;
032import static org.fcrepo.kernel.api.RdfLexicon.RESOURCE;
033import static org.junit.Assert.assertEquals;
034import static org.junit.Assert.assertNotEquals;
035import static org.junit.Assert.assertTrue;
036import static org.mockito.Mockito.times;
037import static org.mockito.Mockito.verify;
038import static org.mockito.Mockito.when;
039import static org.springframework.test.util.ReflectionTestUtils.setField;
040
041import java.io.File;
042import java.net.URI;
043import java.nio.charset.StandardCharsets;
044import java.nio.file.Files;
045import java.time.Instant;
046import java.util.ArrayList;
047import java.util.Arrays;
048import java.util.Calendar;
049import java.util.Collection;
050import java.util.Date;
051import java.util.List;
052import java.util.UUID;
053
054import javax.inject.Inject;
055
056import org.fcrepo.config.FedoraPropsConfig;
057import org.fcrepo.config.ServerManagedPropsMode;
058import org.fcrepo.kernel.api.ContainmentIndex;
059import org.fcrepo.kernel.api.Transaction;
060import org.fcrepo.kernel.api.exception.InteractionModelViolationException;
061import org.fcrepo.kernel.api.exception.RequestWithAclLinkHeaderException;
062import org.fcrepo.kernel.api.identifiers.FedoraId;
063import org.fcrepo.kernel.api.models.ExternalContent;
064import org.fcrepo.kernel.api.models.FedoraResource;
065import org.fcrepo.kernel.api.models.ResourceHeaders;
066import org.fcrepo.kernel.api.observer.EventAccumulator;
067import org.fcrepo.kernel.api.operations.CreateRdfSourceOperation;
068import org.fcrepo.kernel.api.operations.NonRdfSourceOperation;
069import org.fcrepo.kernel.api.operations.NonRdfSourceOperationFactory;
070import org.fcrepo.kernel.api.operations.RdfSourceOperation;
071import org.fcrepo.kernel.api.operations.RdfSourceOperationFactory;
072import org.fcrepo.kernel.api.operations.ResourceOperation;
073import org.fcrepo.kernel.api.services.MembershipService;
074import org.fcrepo.kernel.api.services.ReferenceService;
075import org.fcrepo.kernel.impl.TestTransactionHelper;
076import org.fcrepo.kernel.impl.operations.CreateNonRdfSourceOperation;
077import org.fcrepo.kernel.impl.operations.NonRdfSourceOperationFactoryImpl;
078import org.fcrepo.kernel.impl.operations.RdfSourceOperationFactoryImpl;
079import org.fcrepo.persistence.api.PersistentStorageSession;
080import org.fcrepo.persistence.api.PersistentStorageSessionManager;
081import org.fcrepo.persistence.api.exceptions.PersistentItemNotFoundException;
082import org.fcrepo.search.api.SearchIndex;
083
084import org.apache.commons.io.FileUtils;
085import org.apache.jena.datatypes.xsd.XSDDateTime;
086import org.apache.jena.rdf.model.Model;
087import org.apache.jena.rdf.model.ModelFactory;
088import org.flywaydb.test.annotation.FlywayTest;
089import org.junit.Before;
090import org.junit.Rule;
091import org.junit.Test;
092import org.junit.rules.TemporaryFolder;
093import org.junit.runner.RunWith;
094import org.mockito.ArgumentCaptor;
095import org.mockito.ArgumentMatchers;
096import org.mockito.Captor;
097import org.mockito.InjectMocks;
098import org.mockito.Mock;
099import org.mockito.MockitoAnnotations;
100import org.springframework.test.context.ContextConfiguration;
101import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
102
103/**
104 * @author bseeger
105 */
106@RunWith(SpringJUnit4ClassRunner.class)
107@ContextConfiguration("/containmentIndexTest.xml")
108public class CreateResourceServiceImplTest {
109
110    private static final List<String> STRING_TYPES_NOT_VALID = Arrays.asList(MEMENTO_TYPE, RESOURCE.toString(),
111            FEDORA_RESOURCE.toString());
112
113    private static final List<String> STRING_TYPES_VALID = Arrays.asList(MEMENTO_TYPE, CONTAINER.toString(),
114            BASIC_CONTAINER.toString());
115
116    private final String defaultInteractionModel = DEFAULT_INTERACTION_MODEL.toString();
117
118    @Rule
119    public TemporaryFolder tempFolder = new TemporaryFolder();
120
121    @Mock
122    private PersistentStorageSessionManager psManager;
123
124    private RdfSourceOperationFactory rdfSourceOperationFactory;
125
126    private NonRdfSourceOperationFactory nonRdfSourceOperationFactory;
127
128    @Inject
129    private ContainmentIndex containmentIndex;
130
131    @Mock
132    private SearchIndex searchIndex;
133
134    @Mock
135    private PersistentStorageSession psSession;
136
137    @Mock
138    private ResourceHeaders resourceHeaders;
139
140    @Mock
141    private ExternalContent extContent;
142
143    @Mock
144    private FedoraResource fedoraResource;
145
146    private Transaction transaction;
147
148    @Mock
149    private ReferenceService referenceService;
150
151    @Mock
152    private MembershipService membershipService;
153
154    @Captor
155    private ArgumentCaptor<ResourceOperation> operationCaptor;
156
157    @Mock
158    private EventAccumulator eventAccumulator;
159
160    @InjectMocks
161    private CreateResourceServiceImpl createResourceService;
162
163    private static final String TX_ID = "tx1234";
164
165    private static final String USER_PRINCIPAL = "fedoraUser";
166
167    private static final String FILENAME = "file.html";
168
169    private static final String CONTENT_TYPE = "text/html";
170
171    private static final Long CONTENT_SIZE = 100L;
172
173    private static final String EXTERNAL_CONTENT_TYPE = "text/plain";
174
175    private final Model model = ModelFactory.createDefaultModel();
176
177    private static final Collection<URI> DIGESTS = singleton(URI.create("urn:sha1:12345abced"));
178
179    private static final List<FedoraId> cleanupList = new ArrayList<>() ;
180
181    private final FedoraId rootId = FedoraId.getRepositoryRootId();
182
183    private FedoraPropsConfig propsConfig = new FedoraPropsConfig();
184
185    @Before
186    @FlywayTest
187    public void setUp() {
188        MockitoAnnotations.openMocks(this);
189        rdfSourceOperationFactory = new RdfSourceOperationFactoryImpl();
190        setField(createResourceService, "rdfSourceOperationFactory", rdfSourceOperationFactory);
191        nonRdfSourceOperationFactory = new NonRdfSourceOperationFactoryImpl();
192        setField(createResourceService, "nonRdfSourceOperationFactory", nonRdfSourceOperationFactory);
193        setField(createResourceService, "containmentIndex", containmentIndex);
194        setField(createResourceService, "searchIndex", searchIndex);
195        setField(createResourceService, "eventAccumulator", eventAccumulator);
196        setField(createResourceService, "referenceService", referenceService);
197        setField(createResourceService, "membershipService", membershipService);
198        setField(createResourceService, "fedoraPropsConfig", propsConfig);
199        propsConfig.setServerManagedPropsMode(ServerManagedPropsMode.STRICT);
200        when(psManager.getSession(ArgumentMatchers.any())).thenReturn(psSession);
201        transaction = TestTransactionHelper.mockTransaction(TX_ID, false);
202    }
203
204    /**
205     * Test trying to add a child to a non-existant parent.
206     * We recursive to repository root for a parent, so this is now just creating a ghost node?
207     */
208    @Test
209    public void testNoParentRdf() throws Exception {
210        final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString());
211        final FedoraId childId = fedoraId.resolve("child");
212        when(psSession.getHeaders(fedoraId, null)).thenThrow(PersistentItemNotFoundException.class);
213        createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, model);
214        verify(transaction).lockResource(childId);
215    }
216
217    @Test
218    public void testParentAg() {
219        final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString());
220        final FedoraId childId = fedoraId.resolve("child");
221        containmentIndex.addContainedBy(transaction, FedoraId.getRepositoryRootId(), fedoraId);
222        when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders);
223        when(resourceHeaders.isArchivalGroup()).thenReturn(true);
224        createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, model);
225        verify(transaction).lockResource(fedoraId);
226        verify(transaction).lockResource(childId);
227    }
228
229    @Test
230    public void testParentContainerInAg() {
231        final FedoraId agId = FedoraId.create(UUID.randomUUID().toString());
232        final FedoraId fedoraId = agId.resolve(UUID.randomUUID().toString());
233        final FedoraId childId = fedoraId.resolve("child");
234        containmentIndex.addContainedBy(transaction, agId, fedoraId);
235        when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders);
236        when(resourceHeaders.isArchivalGroup()).thenReturn(false);
237        when(resourceHeaders.getArchivalGroupId()).thenReturn(agId);
238        createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, model);
239        verify(transaction).lockResource(agId);
240        verify(transaction).lockResource(childId);
241    }
242
243    /**
244     * Test creating a RDFSource with a NonRDFSource parent.
245     */
246    @Test(expected = InteractionModelViolationException.class)
247    public void testParentIsBinaryRdf() throws Exception {
248        final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString());
249        final FedoraId childId = fedoraId.resolve("child");
250        containmentIndex.addContainedBy(transaction, rootId, fedoraId);
251        when(resourceHeaders.getInteractionModel()).thenReturn(NON_RDF_SOURCE.toString());
252        when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders);
253        createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, model);
254    }
255
256    /**
257     * Test creating a NonRDFSource with a NonRDFSource parent.
258     */
259    @Test(expected = InteractionModelViolationException.class)
260    public void testParentIsBinary() throws Exception {
261        final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString());
262        final FedoraId childId = fedoraId.resolve("child");
263        containmentIndex.addContainedBy(transaction, rootId, fedoraId);
264        when(resourceHeaders.getInteractionModel()).thenReturn(NON_RDF_SOURCE.toString());
265        when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders);
266        createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, FILENAME, CONTENT_SIZE, null,
267                DIGESTS, null, null);
268    }
269
270    /**
271     * Test creating an external NonRDFSource with a NonRDFSource parent.
272     * TODO: put/post to a binary parent is tested above, might be a duplicate.
273     */
274    @Test(expected = InteractionModelViolationException.class)
275    public void testParentIsExternal() throws Exception {
276        final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString());
277        final FedoraId childId = fedoraId.resolve("child");
278        containmentIndex.addContainedBy(transaction, rootId, fedoraId);
279        when(resourceHeaders.getInteractionModel()).thenReturn(NON_RDF_SOURCE.toString());
280        when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders);
281        createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, FILENAME, CONTENT_SIZE, null,
282                DIGESTS, null, extContent);
283    }
284
285    /**
286     * Test creating a RDFSource with a RDFSource parent.
287     */
288    @Test
289    public void testParentIsRdf() throws Exception {
290        final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString());
291        final FedoraId childId = fedoraId.resolve("child");
292        containmentIndex.addContainedBy(transaction, rootId, fedoraId);
293        when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders);
294        when(resourceHeaders.getInteractionModel()).thenReturn(BASIC_CONTAINER.toString());
295        createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, model);
296        cleanupList.add(fedoraId);
297        verify(psSession).persist(operationCaptor.capture());
298        final FedoraId persistedId = operationCaptor.getValue().getResourceId();
299        assertNotEquals(fedoraId, persistedId);
300        assertTrue(persistedId.getFullId().startsWith(fedoraId.getFullId()));
301        assertEquals(1, containmentIndex.getContains(transaction, fedoraId).count());
302
303        verify(transaction).lockResource(childId);
304    }
305
306    /**
307     * Test creating a NonRDFSource with a RDFSource parent.
308     */
309    @Test
310    public void testParentIsRdfBinary() throws Exception {
311        final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString());
312        final FedoraId childId = fedoraId.resolve("child");
313        containmentIndex.addContainedBy(transaction, rootId, fedoraId);
314        when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders);
315        when(resourceHeaders.getInteractionModel()).thenReturn(BASIC_CONTAINER.toString());
316        createResourceService.perform(transaction, USER_PRINCIPAL, childId, CONTENT_TYPE,
317                FILENAME, CONTENT_SIZE, null, DIGESTS, null, null);
318        cleanupList.add(fedoraId);
319        verify(psSession, times(2)).persist(operationCaptor.capture());
320        final List<ResourceOperation> operations = operationCaptor.getAllValues();
321        final var operation = getOperation(operations, CreateNonRdfSourceOperation.class);
322        final FedoraId persistedId = operation.getResourceId();
323        assertNotEquals(fedoraId, persistedId);
324        assertTrue(persistedId.getFullId().startsWith(fedoraId.getFullId()));
325        assertBinaryPropertiesPresent(operation);
326        assertEquals(fedoraId, operation.getParentId());
327        assertEquals(1, containmentIndex.getContains(transaction, fedoraId).count());
328        verify(transaction).lockResource(childId);
329    }
330
331    /**
332     * Test setting some system properties only accessible in relaxed mode.
333     */
334    @Test
335    public void testRdfSetRelaxedProperties_Post() throws Exception {
336        final var createdDate = Instant.parse("2019-11-12T10:00:30.0Z");
337        final var calendar = Calendar.getInstance();
338        calendar.setTime(Date.from(createdDate));
339        final var createdDateXsd = new XSDDateTime(calendar);
340        final var lastModifiedDate = Instant.parse("2019-11-12T14:11:05.0Z");
341        calendar.setTime(Date.from(lastModifiedDate));
342        final var lastModifiedDateXsd = new XSDDateTime(calendar);
343        final String relaxedUser = "relaxedUser";
344        final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString());
345        final FedoraId childId = fedoraId.resolve("testSlug");
346        containmentIndex.addContainedBy(transaction, rootId, fedoraId);
347
348        final var resc = model.getResource(childId.getFullId());
349        resc.addLiteral(LAST_MODIFIED_DATE, lastModifiedDateXsd);
350        resc.addLiteral(LAST_MODIFIED_BY, relaxedUser);
351        resc.addLiteral(CREATED_DATE, createdDateXsd);
352        resc.addLiteral(CREATED_BY, relaxedUser);
353
354        when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders);
355        when(psSession.getHeaders(childId, null)).thenReturn(resourceHeaders);
356
357        when(resourceHeaders.getInteractionModel()).thenReturn(BASIC_CONTAINER.toString());
358        propsConfig.setServerManagedPropsMode(ServerManagedPropsMode.RELAXED);
359        createResourceService.perform(transaction, USER_PRINCIPAL, childId, null, model);
360        cleanupList.add(fedoraId);
361
362        verify(psSession).persist(operationCaptor.capture());
363
364        final var operation = operationCaptor.getValue();
365        final FedoraId persistedId = operation.getResourceId();
366        assertNotEquals(fedoraId, persistedId);
367        assertTrue(persistedId.getFullId().startsWith(fedoraId.getFullId()));
368        assertEquals(persistedId, childId);
369
370        final var rdfOp = (RdfSourceOperation) operation;
371        assertEquals(relaxedUser, rdfOp.getCreatedBy());
372        assertEquals(relaxedUser, rdfOp.getLastModifiedBy());
373        assertEquals(createdDate, rdfOp.getCreatedDate());
374        assertEquals(lastModifiedDate, rdfOp.getLastModifiedDate());
375        assertEquals(1, containmentIndex.getContains(transaction, fedoraId).count());
376        verify(transaction).lockResource(childId);
377    }
378
379    /**
380     * This test now seems to ensure that the createResourceService will overwrite an existing object
381     * TODO: Review expectations
382     */
383    @Test
384    public void testWithBinary() throws Exception {
385        final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString());
386        final FedoraId childId = fedoraId.resolve("testSlug");
387        containmentIndex.addContainedBy(transaction, FedoraId.getRepositoryRootId(), fedoraId);
388        containmentIndex.commitTransaction(transaction);
389        when(psSession.getHeaders(fedoraId, null)).thenReturn(resourceHeaders);
390        when(psSession.getHeaders(childId, null)).thenReturn(resourceHeaders);
391        when(resourceHeaders.getInteractionModel()).thenReturn(BASIC_CONTAINER.toString());
392        createResourceService.perform(transaction, USER_PRINCIPAL, childId,
393                CONTENT_TYPE, FILENAME, CONTENT_SIZE, null, DIGESTS, null, null);
394        cleanupList.add(fedoraId);
395        verify(psSession, times(2)).persist(operationCaptor.capture());
396        final List<ResourceOperation> operations = operationCaptor.getAllValues();
397        final var operation = getOperation(operations, CreateNonRdfSourceOperation.class);
398        final FedoraId persistedId = operation.getResourceId();
399        assertNotEquals(fedoraId, persistedId);
400        assertEquals(childId, persistedId);
401        assertTrue(persistedId.getFullId().startsWith(fedoraId.getFullId()));
402        assertBinaryPropertiesPresent(operation);
403        assertEquals(fedoraId, operation.getParentId());
404
405        final var descOperation = getOperation(operations, CreateRdfSourceOperation.class);
406        assertEquals(persistedId.asDescription(), descOperation.getResourceId());
407        assertEquals(1, containmentIndex.getContains(transaction, fedoraId).count());
408        verify(transaction).lockResource(childId);
409    }
410
411    @Test
412    public void testSendingValidInteractionModel() {
413        // If you provide a valid interaction model, you should always get it back.
414        final String expected = BASIC_CONTAINER.toString();
415        final String model1 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, false, false, false);
416        assertEquals(expected, model1);
417        final String model2 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, false, false, true);
418        assertEquals(expected, model2);
419        final String model3 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, false, true, false);
420        assertEquals(expected, model3);
421        final String model4 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, false, true, true);
422        assertEquals(expected, model4);
423        final String model5 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, true, false, false);
424        assertEquals(expected, model5);
425        final String model6 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, true, false, true);
426        assertEquals(expected, model6);
427        final String model7 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, true, true, false);
428        assertEquals(expected, model7);
429        final String model8 = createResourceService.determineInteractionModel(STRING_TYPES_VALID, true, true, true);
430        assertEquals(expected, model8);
431    }
432
433    @Test
434    public void testSendingInvalidInteractionModelIsNotRdf() {
435        final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, false, false,
436                false);
437        assertEquals(defaultInteractionModel, model);
438    }
439
440    @Test
441    public void testNotRdfNoContentIsExternal() {
442        final String expected = NON_RDF_SOURCE.toString();
443        final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, false, false,
444                true);
445        assertEquals(expected, model);
446    }
447
448    @Test
449    public void testNotRdfContentPresentNotExternal() {
450        final String expected = NON_RDF_SOURCE.toString();
451        final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, false, true,
452                false);
453        assertEquals(expected, model);
454    }
455
456    @Test
457    public void testNotRdfContentPresentIsExternal() {
458        final String expected = NON_RDF_SOURCE.toString();
459        final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, false, true, true);
460        assertEquals(expected, model);
461    }
462
463    @Test
464    public void testIsRdfNoContentNotExternal() {
465        final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, true, false,
466                false);
467        assertEquals(defaultInteractionModel, model);
468    }
469
470    @Test
471    public void testIsRdfNoContentIsExternal() {
472        final String expected = NON_RDF_SOURCE.toString();
473        final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, true, false, true);
474        assertEquals(expected, model);
475    }
476
477    @Test
478    public void testIsRdfHasContentNotExternal() {
479        final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, true, true, false);
480        assertEquals(defaultInteractionModel, model);
481    }
482
483    @Test
484    public void testIsRdfHasContentIsExternal() {
485        final String expected = NON_RDF_SOURCE.toString();
486        final String model = createResourceService.determineInteractionModel(STRING_TYPES_NOT_VALID, true, true, true);
487        assertEquals(expected, model);
488    }
489
490    @Test(expected = RequestWithAclLinkHeaderException.class)
491    public void testCheckAclLinkHeaderFailDblQ() throws Exception {
492        final List<String> links = Arrays.asList("<" + NON_RDF_SOURCE.toString() + ">; rel=\"type\"",
493                "<http" + "://example.org/some/location/image.tiff>; " + "rel=\"http://fedora" +
494                        ".info/definitions/fcrepo#ExternalContent\"; " + "handling=\"proxy\"; type=\"image/tiff\"",
495                "<http" + "://example.org/some/otherlocation>; rel=\"acl\"");
496        createResourceService.checkAclLinkHeader(links);
497    }
498
499    @Test(expected = RequestWithAclLinkHeaderException.class)
500    public void testCheckAclLinkHeaderFailSingleQ() throws Exception {
501        final List<String> links = Arrays.asList("<" + NON_RDF_SOURCE.toString() + ">; rel=\"type\"",
502                "<http" + "://example.org/some/location/image.tiff>; " + "rel=\"http://fedora" +
503                        ".info/definitions/fcrepo#ExternalContent\"; " + "handling=\"proxy\"; type=\"image/tiff\"",
504                "<http" + "://example.org/some/otherlocation>; rel='acl'");
505        createResourceService.checkAclLinkHeader(links);
506    }
507
508    @Test(expected = RequestWithAclLinkHeaderException.class)
509    public void testCheckAclLinkHeaderFailNoQ() throws Exception {
510        final List<String> links = Arrays.asList("<" + NON_RDF_SOURCE.toString() + ">; rel=\"type\"",
511                "<http" + "://example.org/some/location/image.tiff>; " + "rel=\"http://fedora" +
512                        ".info/definitions/fcrepo#ExternalContent\"; " + "handling=\"proxy\"; type=\"image/tiff\"",
513                "<http" + "://example.org/some/otherlocation>; rel=acl");
514        createResourceService.checkAclLinkHeader(links);
515    }
516
517    @Test
518    public void testCheckAclLinkHeaderSuccess() throws Exception {
519        final List<String> links = Arrays.asList("<" + NON_RDF_SOURCE.toString() + ">; rel=\"type\"",
520                "<http" + "://example.org/some/location/image.tiff>; " + "rel=\"http://fedora" +
521                        ".info/definitions/fcrepo#ExternalContent\"; " + "handling=\"proxy\"; type=\"image/tiff\"");
522        createResourceService.checkAclLinkHeader(links);
523    }
524
525    @Test
526    public void testCopyExternalBinary() throws Exception {
527        final var realDigests = asList(URI.create("urn:sha1:94e66df8cd09d410c62d9e0dc59d3a884e458e05"));
528
529        tempFolder.create();
530        final File externalFile = tempFolder.newFile();
531        final String contentString = "some content";
532        FileUtils.write(externalFile, contentString, StandardCharsets.UTF_8);
533        final URI uri = externalFile.toURI();
534        when(extContent.fetchExternalContent()).thenReturn(Files.newInputStream(externalFile.toPath()));
535        when(extContent.getURI()).thenReturn(uri);
536        when(extContent.isCopy()).thenReturn(true);
537        when(extContent.getHandling()).thenReturn(ExternalContent.COPY);
538        when(extContent.getContentType()).thenReturn("text/plain");
539
540        final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString());
541        final FedoraId childId = fedoraId.resolve("child");
542
543        createResourceService.perform(transaction, USER_PRINCIPAL, childId,
544                CONTENT_TYPE, FILENAME, contentString.length(), null, realDigests, null, extContent);
545
546        verify(psSession, times(2)).persist(operationCaptor.capture());
547        final List<ResourceOperation> operations = operationCaptor.getAllValues();
548        final var operation = getOperation(operations, CreateNonRdfSourceOperation.class);
549        final FedoraId persistedId = operation.getResourceId();
550        assertNotEquals(fedoraId, persistedId);
551        assertTrue(persistedId.getFullId().startsWith(fedoraId.getFullId()));
552        assertBinaryPropertiesPresent(operation, "text/plain", FILENAME, contentString.length(), realDigests);
553        verify(transaction).lockResource(childId);
554    }
555
556    @Test
557    public void testProxyExternalBinary() throws Exception {
558        final var realDigests = asList(URI.create("urn:sha1:94e66df8cd09d410c62d9e0dc59d3a884e458e05"));
559
560        tempFolder.create();
561        final File externalFile = tempFolder.newFile();
562        final String contentString = "some content";
563        FileUtils.write(externalFile, contentString, StandardCharsets.UTF_8);
564        final URI uri = externalFile.toURI();
565        when(extContent.fetchExternalContent()).thenReturn(Files.newInputStream(externalFile.toPath()));
566        when(extContent.getURI()).thenReturn(uri);
567        when(extContent.getHandling()).thenReturn(ExternalContent.PROXY);
568        when(extContent.getContentType()).thenReturn(EXTERNAL_CONTENT_TYPE);
569
570        final FedoraId fedoraId = FedoraId.create(UUID.randomUUID().toString());
571        final FedoraId childId = fedoraId.resolve("child");
572
573        createResourceService.perform(transaction, USER_PRINCIPAL, childId,
574                CONTENT_TYPE, FILENAME, contentString.length(), null, realDigests, null, extContent);
575
576        verify(psSession, times(2)).persist(operationCaptor.capture());
577        final List<ResourceOperation> operations = operationCaptor.getAllValues();
578        final var operation = getOperation(operations, CreateNonRdfSourceOperation.class);
579        final FedoraId persistedId = operation.getResourceId();
580        assertNotEquals(fedoraId, persistedId);
581        assertTrue(persistedId.getFullId().startsWith(fedoraId.getFullId()));
582        assertBinaryPropertiesPresent(operation, EXTERNAL_CONTENT_TYPE, FILENAME, contentString.length(),
583                realDigests);
584
585        assertExternalBinaryPropertiesPresent(operation, uri, ExternalContent.PROXY);
586        verify(transaction).lockResource(childId);
587    }
588
589    private void assertBinaryPropertiesPresent(final ResourceOperation operation, final String exMimetype,
590            final String exFilename, final long exContentSize, final Collection<URI> exDigests) {
591        final var nonRdfOperation = (NonRdfSourceOperation) operation;
592        assertEquals(exContentSize, nonRdfOperation.getContentSize());
593        assertEquals(exFilename, nonRdfOperation.getFilename());
594        assertEquals(exMimetype, nonRdfOperation.getMimeType());
595        assertTrue(exDigests.containsAll(nonRdfOperation.getContentDigests()));
596    }
597
598    private void assertBinaryPropertiesPresent(final ResourceOperation operation) {
599        assertBinaryPropertiesPresent(operation, CONTENT_TYPE, FILENAME, CONTENT_SIZE, DIGESTS);
600    }
601
602    private void assertExternalBinaryPropertiesPresent(final ResourceOperation operation, final URI exExternalUri,
603            final String exHandling) {
604        final var nonRdfOperation = (NonRdfSourceOperation) operation;
605        assertEquals(exExternalUri, nonRdfOperation.getContentUri());
606        assertEquals(exHandling, nonRdfOperation.getExternalHandling());
607    }
608
609    private <T extends ResourceOperation> T getOperation(final List<ResourceOperation> operations,
610            final Class<T> clazz) {
611        return clazz.cast(operations.stream()
612                .filter(o -> (clazz.isInstance(o)))
613                .findFirst()
614                .get());
615    }
616
617}