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.connector.file; 019 020import static org.apache.jena.rdf.model.ResourceFactory.createResource; 021import static java.lang.System.clearProperty; 022import static java.lang.System.getProperty; 023import static java.lang.System.setProperty; 024import static java.nio.file.Files.write; 025import static java.util.Arrays.asList; 026import static com.google.common.collect.Lists.transform; 027import static org.fcrepo.kernel.api.FedoraTypes.CONTENT_SIZE; 028import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_BINARY; 029import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_NON_RDF_SOURCE_DESCRIPTION; 030import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_CONTAINER; 031import static org.fcrepo.kernel.api.RdfLexicon.HAS_MESSAGE_DIGEST; 032import static org.fcrepo.kernel.api.RdfCollectors.toModel; 033import static org.fcrepo.kernel.api.utils.ContentDigest.asURI; 034import static org.fcrepo.kernel.modeshape.utils.FedoraTypesUtils.getJcrNode; 035import static org.junit.Assert.assertEquals; 036import static org.junit.Assert.assertFalse; 037import static org.junit.Assert.assertNotEquals; 038import static org.junit.Assert.assertNotNull; 039import static org.junit.Assert.assertTrue; 040import static org.junit.Assert.fail; 041import static org.modeshape.common.util.SecureHash.getHash; 042import static org.modeshape.common.util.SecureHash.Algorithm.SHA_1; 043import static org.slf4j.LoggerFactory.getLogger; 044 045import java.io.File; 046import java.io.IOException; 047import java.net.URI; 048import java.nio.file.Files; 049import java.nio.file.Paths; 050import java.security.NoSuchAlgorithmException; 051import java.util.Collection; 052import java.util.Iterator; 053 054import javax.inject.Inject; 055import javax.jcr.Node; 056import javax.jcr.Repository; 057import javax.jcr.RepositoryException; 058import javax.jcr.Session; 059import javax.jcr.nodetype.NodeType; 060 061import org.apache.jena.rdf.model.Model; 062 063import org.apache.commons.io.FileUtils; 064import org.apache.commons.io.filefilter.TrueFileFilter; 065import org.apache.commons.io.filefilter.WildcardFileFilter; 066 067import org.fcrepo.kernel.api.models.Container; 068import org.fcrepo.kernel.api.models.FedoraBinary; 069import org.fcrepo.kernel.api.models.FedoraResource; 070import org.fcrepo.kernel.api.services.BinaryService; 071import org.fcrepo.kernel.api.services.ContainerService; 072import org.fcrepo.kernel.api.services.NodeService; 073import org.fcrepo.kernel.modeshape.rdf.impl.DefaultIdentifierTranslator; 074 075import org.junit.AfterClass; 076import org.junit.BeforeClass; 077import org.junit.Test; 078import org.junit.runner.RunWith; 079import org.slf4j.Logger; 080import org.springframework.test.context.ContextConfiguration; 081import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 082 083/** 084 * An abstract suite of tests that should work against any configuration 085 * of a FedoraFileSystemFederation. Tests that only work on certain 086 * configurations (ie, require read/write capabilities) should be implemented 087 * in subclasses. 088 * 089 * @author Andrew Woods 090 * @since 2014-2-3 091 */ 092@ContextConfiguration({"/spring-test/repo.xml"}) 093@RunWith(SpringJUnit4ClassRunner.class) 094public abstract class AbstractFedoraFileSystemConnectorIT { 095 096 @Inject 097 protected Repository repo; 098 099 @Inject 100 protected NodeService nodeService; 101 102 @Inject 103 protected ContainerService containerService; 104 105 @Inject 106 protected BinaryService binaryService; 107 108 /** 109 * Gets the path (relative to the filesystem federation) of a directory 110 * that's expected to be present. 111 * 112 * @return string that contains the path to the dir 113 */ 114 protected abstract String testDirPath(); 115 116 /** 117 * Gets the path (relative to the filesystem federation) of a file 118 * that's expected to be present. 119 * 120 * @return string that contains the path to the file 121 */ 122 protected abstract String testFilePath(); 123 124 /** 125 * The name (relative path) of the federation to be tested. This 126 * must coincide with the "projections" provided in repository.json. 127 * 128 * @return string that contains the path to the federation 129 */ 130 protected abstract String federationName(); 131 132 /** 133 * The filesystem path for the root of the filesystem federation being 134 * tested. This must coincide with the "directoryPath" provided in 135 * repository.json (or the system property that's populating the relevant 136 * configuration". 137 * 138 * @return string that contains the path to root 139 */ 140 protected abstract String getFederationRoot(); 141 142 private final static String PROP_TEST_DIR1 = "fcrepo.test.connector.file.directory1"; 143 private final static String PROP_TEST_DIR2 = "fcrepo.test.connector.file.directory2"; 144 private final static String PROP_EXT_TEST_DIR = "fcrepo.test.connector.file.properties.directory"; 145 146 protected String getReadWriteFederationRoot() { 147 return getProperty(PROP_TEST_DIR1); 148 } 149 150 protected String getReadOnlyFederationRoot() { 151 return getProperty(PROP_TEST_DIR2); 152 } 153 154 private static final Logger logger = 155 getLogger(AbstractFedoraFileSystemConnectorIT.class); 156 157 /** 158 * Sets a system property and ensures artifacts from previous tests are 159 * cleaned up. 160 */ 161 @BeforeClass 162 public static void setSystemPropertiesAndCleanUp() { 163 164 // Instead of creating dummy files over which to federate, 165 // we configure the FedoraFileSystemFederation instances to 166 // point to paths within the "target" directory. 167 final File testDir1 = new File("target/test-classes/obj-dir1"); 168 setProperty(PROP_TEST_DIR1, testDir1.getAbsolutePath()); 169 170 final File testDir2 = new File("target/test-classes/obj-dir2"); 171 cleanUpJsonFilesFiles(testDir2); 172 setProperty(PROP_TEST_DIR2, testDir2.getAbsolutePath()); 173 174 final File testPropertiesDir = new File("target/test-classes-properties"); 175 if (testPropertiesDir.exists()) { 176 cleanUpJsonFilesFiles(testPropertiesDir); 177 } else { 178 testPropertiesDir.mkdir(); 179 } 180 setProperty(PROP_EXT_TEST_DIR, testPropertiesDir.getAbsolutePath()); 181 } 182 183 @AfterClass 184 public static void unsetSystemPropertiesAndCleanUp() { 185 clearProperty(PROP_TEST_DIR1); 186 clearProperty(PROP_TEST_DIR2); 187 clearProperty(PROP_EXT_TEST_DIR); 188 } 189 190 protected static void cleanUpJsonFilesFiles(final File directory) { 191 final WildcardFileFilter filter = new WildcardFileFilter("*.modeshape.json"); 192 final Collection<File> files = FileUtils.listFiles(directory, filter, TrueFileFilter.INSTANCE); 193 final Iterator<File> iterator = files.iterator(); 194 195 // Clean up files persisted in previous runs 196 while (iterator.hasNext()) { 197 final File f = iterator.next(); 198 final String path = f.getAbsolutePath(); 199 try { 200 Files.deleteIfExists(Paths.get(path)); 201 } catch (final IOException e) { 202 logger.error("Error in clean up", e); 203 fail("Unable to delete work files from a previous test run. File=" + path); 204 } 205 } 206 } 207 208 @Test 209 public void testGetFederatedObject() throws RepositoryException { 210 final Session session = repo.login(); 211 212 final Container object = containerService.findOrCreate(session, testDirPath()); 213 assertNotNull(object); 214 215 final Node node = getJcrNode(object); 216 final NodeType[] mixins = node.getMixinNodeTypes(); 217 assertEquals(2, mixins.length); 218 219 final boolean found = transform(asList(mixins), NodeType::getName).contains(FEDORA_CONTAINER); 220 assertTrue("Mixin not found: " + FEDORA_CONTAINER, found); 221 222 session.save(); 223 session.logout(); 224 } 225 226 @Test 227 public void testGetFederatedDatastream() throws RepositoryException { 228 final Session session = repo.login(); 229 230 final FedoraResource description = binaryService.findOrCreate(session, testFilePath()).getDescription(); 231 assertNotNull(description); 232 233 final Node node = getJcrNode(description); 234 final NodeType[] mixins = node.getMixinNodeTypes(); 235 assertEquals(2, mixins.length); 236 237 final boolean found = transform(asList(mixins), NodeType::getName) 238 .contains(FEDORA_NON_RDF_SOURCE_DESCRIPTION); 239 assertTrue("Mixin not found: " + FEDORA_NON_RDF_SOURCE_DESCRIPTION, found); 240 241 session.save(); 242 session.logout(); 243 } 244 245 @Test 246 public void testGetFederatedContent() throws RepositoryException { 247 final Session session = repo.login(); 248 249 final Node node = getJcrNode(nodeService.find(session, testFilePath() + "/jcr:content")); 250 assertNotNull(node); 251 252 final NodeType[] mixins = node.getMixinNodeTypes(); 253 assertEquals(2, mixins.length); 254 255 final boolean found = transform(asList(mixins), NodeType::getName).contains(FEDORA_BINARY); 256 assertTrue("Mixin not found: " + FEDORA_BINARY, found); 257 258 final File file = fileForNode(); 259 260 assertTrue(file.getAbsolutePath(), file.exists()); 261 assertEquals(file.length(), node.getProperty(CONTENT_SIZE).getLong()); 262 263 session.save(); 264 session.logout(); 265 } 266 267 @Test 268 public void testFixity() throws RepositoryException, IOException, NoSuchAlgorithmException { 269 final Session session = repo.login(); 270 271 checkFixity(binaryService.findOrCreate(session, testFilePath())); 272 273 session.save(); 274 session.logout(); 275 } 276 277 @Test 278 public void testChangedFileFixity() throws RepositoryException, IOException, NoSuchAlgorithmException { 279 final Session session = repo.login(); 280 281 final FedoraBinary binary = binaryService.findOrCreate(session, testFilePath()); 282 283 final String originalFixity = checkFixity(binary); 284 285 final File file = fileForNode(); 286 write(file.toPath(), " ".getBytes("UTF-8")); 287 288 final String newFixity = checkFixity(binary); 289 290 assertNotEquals("Checksum is expected to have changed!", originalFixity, newFixity); 291 292 session.save(); 293 session.logout(); 294 } 295 296 private String checkFixity(final FedoraBinary binary) 297 throws IOException, NoSuchAlgorithmException, RepositoryException { 298 assertNotNull(binary); 299 300 final File file = fileForNode(); 301 final byte[] hash = getHash(SHA_1, file); 302 303 final URI calculatedChecksum = asURI(SHA_1.toString(), hash); 304 305 final DefaultIdentifierTranslator graphSubjects = new DefaultIdentifierTranslator(repo.login()); 306 final Model results = binary.getFixity(graphSubjects).collect(toModel()); 307 assertNotNull(results); 308 309 assertFalse("Found no results!", results.isEmpty()); 310 311 312 assertTrue("Expected to find checksum", 313 results.contains(null, 314 HAS_MESSAGE_DIGEST, 315 createResource(calculatedChecksum.toString()))); 316 317 return calculatedChecksum.toString(); 318 } 319 320 protected File fileForNode() { 321 return new File(getFederationRoot(), testFilePath().replace(federationName(), "")); 322 } 323 324 /** 325 * The following is painfully tied to some implementation details 326 * but it's critical that we test that the json files are actually written 327 * somewhere, so it's the best I can do without further opening up the 328 * internals of JsonSidecarExtraPropertiesStore. 329 * 330 * @param node The node to access for the file reference 331 * @return A reference to the nodes property file 332 */ 333 protected File propertyFileForNode(final Node node) { 334 try { 335 logger.debug("NODE PATH: " + node.getPath()); 336 } catch (final RepositoryException e) { 337 e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. 338 } 339 return new File(getProperty(PROP_EXT_TEST_DIR), 340 testFilePath().replace(federationName(), "") + ".modeshape.json"); 341 } 342}