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