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.client.integration; 019 020import static javax.ws.rs.core.Response.Status.CREATED; 021import static org.apache.jena.rdf.model.ResourceFactory.createProperty; 022import static org.apache.jena.rdf.model.ResourceFactory.createResource; 023import static org.fcrepo.client.FedoraHeaderConstants.MEMENTO_DATETIME; 024import static org.fcrepo.client.FedoraTypes.MEMENTO_TYPE; 025import static org.fcrepo.client.HeaderHelpers.UTC_RFC_1123_FORMATTER; 026import static org.fcrepo.client.LinkHeaderConstants.DESCRIBEDBY_REL; 027import static org.fcrepo.client.LinkHeaderConstants.MEMENTO_TIME_MAP_REL; 028import static org.fcrepo.client.TestUtils.rdfTtl; 029import static org.junit.Assert.assertEquals; 030import static org.junit.Assert.assertTrue; 031 032import java.io.ByteArrayInputStream; 033import java.io.ByteArrayOutputStream; 034import java.io.InputStream; 035import java.net.URI; 036import java.time.Instant; 037import java.time.LocalDateTime; 038import java.time.ZoneOffset; 039import java.time.ZonedDateTime; 040 041import org.apache.commons.io.IOUtils; 042import org.apache.jena.rdf.model.Model; 043import org.apache.jena.rdf.model.Property; 044import org.apache.jena.rdf.model.Resource; 045import org.fcrepo.client.FcrepoClient; 046import org.fcrepo.client.FcrepoResponse; 047import org.fcrepo.kernel.api.RdfLexicon; 048import org.junit.Test; 049import org.junit.runner.RunWith; 050import org.springframework.test.context.ContextConfiguration; 051import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 052 053/** 054 * @author bbpennel 055 */ 056@RunWith(SpringJUnit4ClassRunner.class) 057@ContextConfiguration("/spring-test/test-container.xml") 058public class VersioningIT extends AbstractResourceIT { 059 060 private static final Property DC_TITLE = createProperty("http://purl.org/dc/elements/1.1/title"); 061 062 private final ZonedDateTime HISTORIC_DATETIME = LocalDateTime.of(2000, 1, 1, 0, 0).atZone(ZoneOffset.UTC); 063 064 private final String HISTORIC_TIMESTAMP = UTC_RFC_1123_FORMATTER.format(HISTORIC_DATETIME); 065 066 public VersioningIT() { 067 super(); 068 069 client = FcrepoClient.client() 070 .credentials("fedoraAdmin", "password") 071 .authScope("localhost") 072 .build(); 073 } 074 075 @Test 076 public void testCreateMementoFromOriginal() throws Exception { 077 // Create original resource with custom property 078 final FcrepoResponse createOriginalResp = client.post(new URI(serverAddress)) 079 .body(new ByteArrayInputStream(rdfTtl.getBytes()), "text/turtle") 080 .perform(); 081 082 final URI timemapURI = createOriginalResp.getLinkHeaders(MEMENTO_TIME_MAP_REL).get(0); 083 084 final FcrepoResponse mementoResp = client.createMemento(timemapURI) 085 .perform(); 086 assertEquals(CREATED.getStatusCode(), mementoResp.getStatusCode()); 087 088 final URI mementoURI = mementoResp.getLocation(); 089 090 final FcrepoResponse getResp = client.get(mementoURI).perform(); 091 assertTrue("Retrieved object must be a memento", getResp.hasType(MEMENTO_TYPE)); 092 final Model respModel = getResponseModel(getResp); 093 assertTrue(respModel.contains(null, DC_TITLE, "Test Object")); 094 } 095 096 // create memento from historic version, binary 097 @Test 098 public void testCreateHistoricBinaryMemento() throws Exception { 099 // Create original binary 100 final String mimetype = "text/plain"; 101 final String bodyContent = "Hello world"; 102 final FcrepoResponse createOriginalResp = client.post(new URI(serverAddress)) 103 .body(new ByteArrayInputStream(bodyContent.getBytes()), mimetype) 104 .perform(); 105 106 final URI originalURI = createOriginalResp.getLocation(); 107 final URI timemapURI = createOriginalResp.getLinkHeaders(MEMENTO_TIME_MAP_REL).get(0); 108 109 // Create memento of the binary 110 final String mementoType = "text/old"; 111 final String mementoContent = "Hello old world"; 112 final FcrepoResponse binMementoResp = client.createMemento(timemapURI, HISTORIC_TIMESTAMP) 113 .body(new ByteArrayInputStream(mementoContent.getBytes()), mementoType) 114 .perform(); 115 assertEquals(CREATED.getStatusCode(), binMementoResp.getStatusCode()); 116 117 // Determine location of description and its timemap 118 final URI descURI = binMementoResp.getLinkHeaders(DESCRIBEDBY_REL).get(0); 119 final URI descTimeMapURI = client.head(descURI).perform().getLinkHeaders(MEMENTO_TIME_MAP_REL).get(0); 120 121 // Create memento of the binary description 122 final String titleValue = "Ancient Binary"; 123 // Modify the original model before creating memento 124 final Model originalModel = getResponseModel(client.get(descURI).perform()); 125 final Resource resc = originalModel.getResource(originalURI.toString()); 126 resc.addLiteral(DC_TITLE, titleValue); 127 // Set the updated mimetype for the memento 128 resc.removeAll(RdfLexicon.HAS_MIME_TYPE); 129 resc.addLiteral(RdfLexicon.HAS_MIME_TYPE, mementoType); 130 131 final FcrepoResponse descMementoResp = client.createMemento(descTimeMapURI, HISTORIC_TIMESTAMP) 132 .body(modelToInputStream(originalModel), "text/turtle") 133 .perform(); 134 assertEquals(CREATED.getStatusCode(), descMementoResp.getStatusCode()); 135 136 // Verify the historic binary was created 137 final URI binaryMementoURI = binMementoResp.getLocation(); 138 final FcrepoResponse getBinMementoResp = client.get(binaryMementoURI).perform(); 139 final String getBinContent = IOUtils.toString(getBinMementoResp.getBody(), "UTF-8"); 140 141 assertEquals(mementoContent, getBinContent); 142 assertEquals(mementoType, getBinMementoResp.getContentType()); 143 assertTrue("Retrieved object must be a memento", getBinMementoResp.hasType(MEMENTO_TYPE)); 144 assertEquals("Memento did not have expected datetime", 145 HISTORIC_TIMESTAMP, getBinMementoResp.getHeaderValue(MEMENTO_DATETIME)); 146 147 // Find the memento of the description by its memento datetime 148 final FcrepoResponse mementoDescNegoResp = client.get(descURI) 149 .acceptDatetime(HISTORIC_TIMESTAMP) 150 .perform(); 151 152 assertEquals("Memento did not have expected datetime", 153 HISTORIC_TIMESTAMP, mementoDescNegoResp.getHeaderValue(MEMENTO_DATETIME)); 154 final Model respModel = getResponseModel(mementoDescNegoResp); 155 assertTrue("Memento must contain provided property", 156 respModel.contains(createResource(originalURI.toString()), DC_TITLE, titleValue)); 157 } 158 159 // create memento from historic version, container 160 @Test 161 public void testCreateHistoricContainerMemento() throws Exception { 162 // Create original resource with custom property 163 final FcrepoResponse createOriginalResp = client.post(new URI(serverAddress)) 164 .perform(); 165 166 final URI originalURI = createOriginalResp.getLocation(); 167 final URI timemapURI = createOriginalResp.getLinkHeaders(MEMENTO_TIME_MAP_REL).get(0); 168 169 // Add a property to the resource to use as the historic version 170 final String titleValue = "Very Historical"; 171 final FcrepoResponse originalResp = client.get(originalURI).perform(); 172 final Model originalModel = getResponseModel(originalResp); 173 originalModel.getResource(originalURI.toString()) 174 .addLiteral(DC_TITLE, titleValue); 175 176 // Create historic memento with updated model 177 final FcrepoResponse mementoResp = client.createMemento(timemapURI, HISTORIC_DATETIME.toInstant()) 178 .body(modelToInputStream(originalModel), "text/turtle") 179 .perform(); 180 assertEquals(CREATED.getStatusCode(), mementoResp.getStatusCode()); 181 182 // Verify that the memento matches expectations 183 final URI mementoURI = mementoResp.getLocation(); 184 final FcrepoResponse getResp = client.get(mementoURI).perform(); 185 assertTrue("Retrieved object must be a memento", getResp.hasType(MEMENTO_TYPE)); 186 assertEquals("Memento did not have expected datetime", 187 HISTORIC_TIMESTAMP, getResp.getHeaderValue(MEMENTO_DATETIME)); 188 final Model respModel = getResponseModel(getResp); 189 assertTrue("Memento must contain provided property", 190 respModel.contains(null, DC_TITLE, titleValue)); 191 } 192 193 @Test 194 public void testDatetimeNegotiation() throws Exception { 195 final FcrepoResponse createOriginalResp = client.post(new URI(serverAddress)) 196 .perform(); 197 198 final URI location = createOriginalResp.getLocation(); 199 final URI timemapURI = createOriginalResp.getLinkHeaders(MEMENTO_TIME_MAP_REL).get(0); 200 // create version 201 client.createMemento(timemapURI).perform(); 202 203 // Negotiate for the most recent memento 204 final FcrepoResponse negotiateResp = client.get(location) 205 .acceptDatetime(Instant.now()) 206 .perform(); 207 208 assertTrue(negotiateResp.hasType(MEMENTO_TYPE)); 209 } 210 211 private InputStream modelToInputStream(final Model model) { 212 final ByteArrayOutputStream out = new ByteArrayOutputStream(); 213 model.write(out, "text/turtle"); 214 return new ByteArrayInputStream(out.toByteArray()); 215 } 216}