Skip to content

Commit

Permalink
Add FedoraFileSystemConnector, so that federated properties and fixit…
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew Woods committed Feb 4, 2014
1 parent c9e6388 commit 7fadf00
Show file tree
Hide file tree
Showing 9 changed files with 631 additions and 0 deletions.
70 changes: 70 additions & 0 deletions fcrepo-connector-file/pom.xml
@@ -0,0 +1,70 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.fcrepo</groupId>
<artifactId>fcrepo</artifactId>
<version>4.0.0-alpha-4-SNAPSHOT</version>
</parent>
<artifactId>fcrepo-connector-file</artifactId>
<name>Fedora Repository FileSystem Connector Module</name>
<description>The Fedora Commons repository filesystem connector module: Provides repository projection over hierarchical files/directories on the filesystem.</description>
<packaging>jar</packaging>

<dependencies>

<dependency>
<groupId>org.fcrepo</groupId>
<artifactId>fcrepo-kernel</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>javax.jcr</groupId>
<artifactId>jcr</artifactId>
</dependency>

<dependency>
<groupId>org.fcrepo</groupId>
<artifactId>fcrepo-http-api</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.fcrepo</groupId>
<artifactId>fcrepo-http-commons</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.fcrepo</groupId>
<artifactId>fcrepo-http-commons</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

</project>
@@ -0,0 +1,143 @@
/**
* Copyright 2013 DuraSpace, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fcrepo.connector.file;

import org.fcrepo.kernel.utils.ContentDigest;
import org.infinispan.schematic.document.Document;
import org.modeshape.connector.filesystem.FileSystemConnector;
import org.modeshape.jcr.api.JcrConstants;
import org.modeshape.jcr.federation.spi.DocumentReader;
import org.modeshape.jcr.federation.spi.DocumentWriter;
import org.modeshape.jcr.value.BinaryValue;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.Property;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

import static org.fcrepo.jcr.FedoraJcrTypes.CONTENT_DIGEST;
import static org.fcrepo.jcr.FedoraJcrTypes.CONTENT_SIZE;
import static org.fcrepo.jcr.FedoraJcrTypes.FEDORA_BINARY;
import static org.fcrepo.jcr.FedoraJcrTypes.FEDORA_DATASTREAM;
import static org.fcrepo.jcr.FedoraJcrTypes.FEDORA_RESOURCE;
import static org.fcrepo.jcr.FedoraJcrTypes.JCR_CREATED;
import static org.fcrepo.jcr.FedoraJcrTypes.JCR_LASTMODIFIED;
import static org.modeshape.jcr.api.JcrConstants.JCR_DATA;
import static org.modeshape.jcr.api.JcrConstants.JCR_PRIMARY_TYPE;

/**
* This class extends the {@link FileSystemConnector} to enable the autocreation of Fedora-specific datastream and
* content properties.
*
* @author Andrew Woods
* Date: 1/30/14
*/
public class FedoraFileSystemConnector extends FileSystemConnector {

private static final Logger LOGGER = LoggerFactory.getLogger(FedoraFileSystemConnector.class);


/**
* This method returns the object/document for the node with the federated arg 'id'.
* <p/>
* Additionally, this method adds Fedora datastream and content properties to the result of the parent class
* implementation.
*/
@Override
public Document getDocumentById(String id) {
LOGGER.debug("Getting Federated document: {}", id);
if (null == id || id.isEmpty()) {
LOGGER.warn("Can not get document with null id");
return null;
}

final Document doc = super.getDocumentById(id);

final DocumentReader docReader = readDocument(doc);
final DocumentWriter docWriter = writeDocument(doc);

final String primaryType = docReader.getPrimaryTypeName();

if (!docReader.getMixinTypeNames().contains(FEDORA_RESOURCE)) {
LOGGER.trace("Adding mixin: {}, to {}", FEDORA_RESOURCE, id);
docWriter.addMixinType(FEDORA_RESOURCE);
}

// Is Fedora Datastream?
if (primaryType.equals(JcrConstants.NT_FILE)) {
decorateDatastreamNode(docReader, docWriter);

// Is Fedora Content?
} else if (primaryType.equals(JcrConstants.NT_RESOURCE)) {
decorateContentNode(docReader, docWriter);
}

// Persist new properties
if (!isReadonly()) {
saveProperties(docReader);
}

return docWriter.document();
}

private void decorateDatastreamNode(DocumentReader docReader, DocumentWriter docWriter) {
if (!docReader.getMixinTypeNames().contains(FEDORA_DATASTREAM)) {
LOGGER.trace("Adding mixin: {}, to {}", FEDORA_DATASTREAM, docReader.getDocumentId());
docWriter.addMixinType(FEDORA_DATASTREAM);
}
}

private void decorateContentNode(DocumentReader docReader, DocumentWriter docWriter) {
if (!docReader.getMixinTypeNames().contains(FEDORA_BINARY)) {
LOGGER.trace("Adding mixin: {}, to {}", FEDORA_BINARY, docReader.getDocumentId());
docWriter.addMixinType(FEDORA_BINARY);
}

if (null == docReader.getProperty(CONTENT_DIGEST)) {

This comment has been minimized.

Copy link
@ajs6f

ajs6f Feb 6, 2014

Contributor

From this point forward, are we clear as to whether every getProperty call on the docReader is actually going back to the federated persistence (which could be really expensive and would make us want to pull all the properties at once) or whether MODE is unmarshalling them all at once and we don't need to worry about it?

This comment has been minimized.

Copy link
@awoods

awoods Feb 6, 2014

A Modeshape "Document" is an in-memory datastructure for the JCR Node. Calls to docReader.getProperty() should be very cheap.

This comment has been minimized.

Copy link
@ajs6f

ajs6f Feb 6, 2014

Contributor

Groovy. That addresses my concern entirely.

final BinaryValue binaryValue = getBinaryValue(docReader);
final String dsChecksum = binaryValue.getHexHash();
final String dsURI = ContentDigest.asURI("SHA-1", dsChecksum).toString();

LOGGER.trace("Adding {} property of {} to {}", CONTENT_DIGEST, dsURI, docReader.getDocumentId());
docWriter.addProperty(CONTENT_DIGEST, dsURI);
}

if (null == docReader.getProperty(CONTENT_SIZE)) {
final BinaryValue binaryValue = getBinaryValue(docReader);
final long binarySize = binaryValue.getSize();

LOGGER.trace("Adding {} property of {} to {}", CONTENT_SIZE, binarySize, docReader.getDocumentId());
docWriter.addProperty(CONTENT_SIZE, binarySize);
}

LOGGER.debug("Decorated data property at path: {}", docReader.getDocumentId());
}

private BinaryValue getBinaryValue(DocumentReader docReader) {
final Property binaryProperty = docReader.getProperty(JCR_DATA);
return (BinaryValue) binaryProperty.getFirstValue();
}

private void saveProperties(DocumentReader docReader) {
LOGGER.trace("Persisting properties for {}", docReader.getDocumentId());
final Map<Name, Property> properties = docReader.getProperties();
final ExtraProperties extraProperties = extraPropertiesFor(docReader.getDocumentId(), true);
extraProperties.addAll(properties).except(JCR_PRIMARY_TYPE, JCR_CREATED, JCR_LASTMODIFIED, JCR_DATA);
extraProperties.save();
}

}
@@ -0,0 +1,145 @@
/**
* Copyright 2013 DuraSpace, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fcrepo.connector.file;

import org.junit.AfterClass;
import org.junit.Assert;
import org.fcrepo.http.commons.test.util.TestHelpers;
import org.infinispan.schematic.document.Document;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.modeshape.jcr.ExecutionContext;
import org.modeshape.jcr.api.JcrConstants;
import org.modeshape.jcr.api.nodetype.NodeTypeManager;
import org.modeshape.jcr.cache.document.DocumentTranslator;
import org.modeshape.jcr.federation.spi.ExtraPropertiesStore;
import org.modeshape.jcr.value.BinaryValue;
import org.modeshape.jcr.value.NameFactory;
import org.modeshape.jcr.value.Property;
import org.modeshape.jcr.value.ValueFactories;
import org.modeshape.jcr.value.basic.BasicName;

import javax.jcr.NamespaceRegistry;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when;
import static org.modeshape.jcr.api.JcrConstants.JCR_DATA;

/**
* @author Andrew Woods
* Date: 2/3/14
*/
public class FedoraFileSystemConnectorTest {

private FedoraFileSystemConnector connector;

private final static String directoryPath = System.getProperty("java.io.tmpdir");
private final static File tmpFile = new File(directoryPath, "fedora-filesystemtest.txt");

This comment has been minimized.

Copy link
@ajs6f

ajs6f Feb 6, 2014

Contributor

Why not just:

http://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#createTempFile(java.nio.file.Path, java.lang.String, java.lang.String, java.nio.file.attribute.FileAttribute...)

@Mock
private NamespaceRegistry mockRegistry;

@Mock
private NodeTypeManager mockNodeTypeManager;

@Mock
private DocumentTranslator mockTranslator;

@Mock
private NameFactory mockNameFactory;

@Mock
private ValueFactories mockValueFactories;

@Mock
private ExtraPropertiesStore mockExtraPropertiesStore;

private ExecutionContext mockContext = new ExecutionContext();


@BeforeClass
public static void beforeClass()
{
try {
FileOutputStream outputStream = new FileOutputStream(tmpFile);
outputStream.write("hello".getBytes());
} catch (IOException e) {
System.err.println("Error creating: " + tmpFile.getAbsolutePath() + " - " + e.getMessage());
}
}

@AfterClass
public static void afterClass() {
try {
tmpFile.delete();
} catch (Exception e) {
System.err.println("Error deleting: " + tmpFile.getAbsolutePath() + " - " + e.getMessage());
}
}

@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);

connector = new FedoraFileSystemConnector();
TestHelpers.setField(connector, "directoryPath", directoryPath);
TestHelpers.setField(connector, "translator", mockTranslator);
TestHelpers.setField(connector, "context", mockContext);
TestHelpers.setField(connector, "extraPropertiesStore", mockExtraPropertiesStore);

TestHelpers.setField(mockTranslator, "names", mockNameFactory);
connector.initialize(mockRegistry, mockNodeTypeManager);

This comment has been minimized.

Copy link
@ajs6f

ajs6f Feb 6, 2014

Contributor

Am I the only person on the team who prefers the use of static imports for code like this? Just, setField. It's obviously what it's doing-- why namespace it over and over again. Just a pet peeve.

}

@Test
public void testGetDocumentByIdNull() throws Exception {
Document doc = connector.getDocumentById(null);
Assert.assertNull(doc);
}

@Test
public void testGetDocumentByIdDatastream() throws Exception {
when(mockTranslator.getPrimaryTypeName(any(Document.class))).thenReturn(JcrConstants.NT_FILE);
when(mockNameFactory.create(anyString())).thenReturn(new BasicName("", tmpFile.getName()));

Document doc = connector.getDocumentById("/" + tmpFile.getName());
Assert.assertNotNull(doc);
}

@Test
public void testGetDocumentByIdContent() throws Exception {
when(mockTranslator.getPrimaryTypeName(any(Document.class))).thenReturn(JcrConstants.NT_RESOURCE);
when(mockNameFactory.create(anyString())).thenReturn(new BasicName("", tmpFile.getName()));

Property binaryProperty = Mockito.mock(Property.class, "BinaryProperty");
BinaryValue binaryValue = Mockito.mock(BinaryValue.class, "BinayValue");
when(binaryProperty.getFirstValue()).thenReturn(binaryValue);
when(mockTranslator.getProperty(any(Document.class), Mockito.eq(JCR_DATA))).thenReturn(binaryProperty);

Document doc = connector.getDocumentById("/" + tmpFile.getName());
Assert.assertNotNull(doc);
}

}

0 comments on commit 7fadf00

Please sign in to comment.