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