From e67dca98a75b7d29001e6f460742c41d95522267 Mon Sep 17 00:00:00 2001 From: vac765 Date: Mon, 13 Dec 2021 13:06:44 -0500 Subject: [PATCH 1/8] NTuple Omit Prefix Implementation --- .../core/extension/OcflExtensionRegistry.java | 6 +- ...TupleOmitPrefixStorageLayoutExtension.java | 156 +++++++++++ .../NTupleOmitPrefixStorageLayoutConfig.java | 180 +++++++++++++ .../NTupleOmitPrefixStorageExtensionTest.java | 252 ++++++++++++++++++ 4 files changed, 592 insertions(+), 2 deletions(-) create mode 100644 ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageLayoutExtension.java create mode 100644 ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/config/NTupleOmitPrefixStorageLayoutConfig.java create mode 100644 ocfl-java-core/src/test/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageExtensionTest.java diff --git a/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/OcflExtensionRegistry.java b/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/OcflExtensionRegistry.java index a3a4b652..322dbc43 100644 --- a/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/OcflExtensionRegistry.java +++ b/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/OcflExtensionRegistry.java @@ -28,6 +28,7 @@ import edu.wisc.library.ocfl.api.util.Enforce; import edu.wisc.library.ocfl.core.extension.storage.layout.FlatLayoutExtension; import edu.wisc.library.ocfl.core.extension.storage.layout.FlatOmitPrefixLayoutExtension; +import edu.wisc.library.ocfl.core.extension.storage.layout.NTupleOmitPrefixStorageLayoutExtension; import edu.wisc.library.ocfl.core.extension.storage.layout.HashedNTupleIdEncapsulationLayoutExtension; import edu.wisc.library.ocfl.core.extension.storage.layout.HashedNTupleLayoutExtension; import org.slf4j.Logger; @@ -55,8 +56,9 @@ public final class OcflExtensionRegistry { HashedNTupleLayoutExtension.EXTENSION_NAME, HashedNTupleLayoutExtension.class, HashedNTupleIdEncapsulationLayoutExtension.EXTENSION_NAME, HashedNTupleIdEncapsulationLayoutExtension.class, FlatLayoutExtension.EXTENSION_NAME, FlatLayoutExtension.class, - FlatOmitPrefixLayoutExtension.EXTENSION_NAME, FlatOmitPrefixLayoutExtension.class - )); + FlatOmitPrefixLayoutExtension.EXTENSION_NAME, FlatOmitPrefixLayoutExtension.class, + NTupleOmitPrefixStorageLayoutExtension.EXTENSION_NAME, NTupleOmitPrefixStorageLayoutExtension.class + )); private OcflExtensionRegistry() { diff --git a/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageLayoutExtension.java b/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageLayoutExtension.java new file mode 100644 index 00000000..2c443ff7 --- /dev/null +++ b/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageLayoutExtension.java @@ -0,0 +1,156 @@ +/** + Copyright @2021 President and Fellows of Harvard College + + 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 edu.wisc.library.ocfl.core.extension.storage.layout; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import edu.wisc.library.ocfl.core.extension.storage.layout.config.NTupleOmitPrefixStorageLayoutConfig; +import edu.wisc.library.ocfl.core.extension.OcflExtensionConfig; +import edu.wisc.library.ocfl.core.extension.storage.layout.OcflStorageLayoutExtension; + +/** + * Implementation of the + * N Tuple Storage Layout extension. + * + * @author vcrema + */ +public class NTupleOmitPrefixStorageLayoutExtension implements OcflStorageLayoutExtension { + + public static final String EXTENSION_NAME = "0007-n-tuple-omit-prefix-storage-layout"; + + private static final Logger LOG = LoggerFactory.getLogger(NTupleOmitPrefixStorageLayoutExtension.class); + + private NTupleOmitPrefixStorageLayoutConfig config; + + /** + * {@inheritDoc} + */ + @Override + public String getExtensionName() { + return EXTENSION_NAME; + } + + /** + * {@inheritDoc} + */ + @Override + public String getDescription() { + return "This storage root extension describes an OCFL storage layout combining a pairtree-like root directory structure derived from prefix-omitted object identifiers, followed by the prefix-omitted object identifier themselves. The OCFL object identifiers are expected to contain prefixes which are removed in the mapping to directory names. The OCFL object identifier prefix is defined as all characters before and including a configurable delimiter. Where the prefix-omitted identifier length is less than tuple size * number of tuples, the remaining object id (prefix omitted) is left or right-side, zero-padded (configurable, left default), or not padded (none), and optionally reversed (default false). The object id is then divided into N n-tuple segments, and used to create nested paths under the OCFL storage root, followed by the prefix-omitted object id directory."; + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void init(OcflExtensionConfig config) { + // Only set this.config if it is uninitialized + if (this.config == null) { + + // Is arg config null? + if (config == null) { + throw new IllegalArgumentException("Arg config must not be null!"); + } + + if (!(config instanceof NTupleOmitPrefixStorageLayoutConfig)) { + throw new IllegalArgumentException(String.format("This extension only supports %s configuration. Received: %s", + getExtensionConfigClass(), config)); + } + + NTupleOmitPrefixStorageLayoutConfig castConfig = (NTupleOmitPrefixStorageLayoutConfig) config; + + validateConfig(castConfig); + this.config = castConfig; + } + } + + private static void validateConfig(NTupleOmitPrefixStorageLayoutConfig config) { + if (config != null) { + if (StringUtils.isBlank(config.getDelimiter())) { + throw new RuntimeException("Delimiter configuration must not be empty!"); + } + if (config.getTupleSize() <= 0) { + throw new RuntimeException("Character count configuration must not less than 0! Value given:" + config.getTupleSize()); + } + if (config.getNumberOfTuples() <= 0) { + throw new RuntimeException("Number of tuples configuration must not less than 0! Value given: " + config.getNumberOfTuples()); + } + if (!config.getZeroPadding().equals(NTupleOmitPrefixStorageLayoutConfig.ZERO_PADDING_LEFT) + && !config.getZeroPadding().equals(NTupleOmitPrefixStorageLayoutConfig.ZERO_PADDING_RIGHT) + && !config.getZeroPadding().equals(NTupleOmitPrefixStorageLayoutConfig.ZERO_PADDING_NONE)) { + throw new RuntimeException("Arg must not 'left', 'right', or 'none': 'zeroPadding'. Value given: " + config.getZeroPadding()); + } + } + } + + @Override + public Class getExtensionConfigClass() { + return NTupleOmitPrefixStorageLayoutConfig.class; + } + + /** + * {@inheritDoc} + */ + @Override + public String mapObjectId(String objectId) { + if (config == null) { + throw new RuntimeException("This extension must be initialized before it can be used."); + } + if (!objectId.contains(config.getDelimiter())) { + throw new RuntimeException("The delimiter " + config.getDelimiter() + " cannot be found in " + objectId + "."); + } + //Split by delimiter and get the last part + String[] parts = StringUtils. splitByWholeSeparator(objectId, config.getDelimiter()); + String section = parts[parts.length - 1]; + + if (section.length() == 0) { + throw new RuntimeException("The delimiter " + config.getDelimiter() + " is only found at the end of " + objectId + "."); + } + + if (config.reverseObjectRoot()) { + //Reverse the section + section = new StringBuilder(section).reverse().toString(); + } + //Add padding if needed and requested + if (section.length() < config.getTupleSize() * config.getNumberOfTuples()) { + + int paddingAmount = config.getTupleSize() * config.getNumberOfTuples(); + if (config.getZeroPadding().equals(NTupleOmitPrefixStorageLayoutConfig.ZERO_PADDING_LEFT)) { + section = StringUtils.leftPad(section, paddingAmount, "0"); + } + else if (config.getZeroPadding().equals(NTupleOmitPrefixStorageLayoutConfig.ZERO_PADDING_RIGHT)) { + section = StringUtils.rightPad(section, paddingAmount, "0"); + } + //Throw runtime exception since we can't pad and there won't be enough characters for the pattern + else { + throw new RuntimeException("Zero padding is set to 'none' but " + section + " is too short to follow the requested tuple pattern: " + config.toString()); + } + } + StringBuilder pathBuilder = new StringBuilder(); + //Split into even sections + for (int i = 0; i < config.getNumberOfTuples(); i++) { + int start = i * config.getTupleSize(); + int end = start + config.getTupleSize(); + pathBuilder.append(section, start, end).append("/"); + } + + //Append the original object id after the delimiter + pathBuilder.append(parts[parts.length - 1]); + return pathBuilder.toString(); + } + +} \ No newline at end of file diff --git a/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/config/NTupleOmitPrefixStorageLayoutConfig.java b/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/config/NTupleOmitPrefixStorageLayoutConfig.java new file mode 100644 index 00000000..1e7ef014 --- /dev/null +++ b/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/config/NTupleOmitPrefixStorageLayoutConfig.java @@ -0,0 +1,180 @@ +package edu.wisc.library.ocfl.core.extension.storage.layout.config; + +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import org.apache.commons.lang3.StringUtils; + +import edu.wisc.library.ocfl.core.extension.storage.layout.NTupleOmitPrefixStorageLayoutExtension; +import edu.wisc.library.ocfl.core.extension.OcflExtensionConfig; + +/** + * Configuration for the + * N Tuple Storage Layout extension. + * + * @author vcrema + * @since 2021-10-25 + */ +public class NTupleOmitPrefixStorageLayoutConfig implements OcflExtensionConfig { + + public static final String ZERO_PADDING_LEFT = "left"; + public static final String ZERO_PADDING_RIGHT = "right"; + public static final String ZERO_PADDING_NONE = "none"; + + private String delimiter; + private int tupleSize = 3; + private int numberOfTuples = 3; + private String zeroPadding = ZERO_PADDING_LEFT; + private boolean reverseObjectRoot = false; + + @JsonIgnore + @Override + public String getExtensionName() { + return NTupleOmitPrefixStorageLayoutExtension.EXTENSION_NAME; + } + + @JsonIgnore + @Override + public boolean hasParameters() { + return true; + } + + /** + * @return the delimiter marking end of prefix + */ + public String getDelimiter() { + return delimiter; + } + + /** + * @return tupleSize - the segment size (in characters) to split the digest + * into + */ + public int getTupleSize() { + return tupleSize; + } + + /** + * @return The number of segments to use for path generation + */ + public int getNumberOfTuples() { + return numberOfTuples; + } + + /** + * return zeroPadding - Indicates whether to use left or right zero padding + * for ids less than tupleSize * numberOfTuples + */ + public String getZeroPadding() { + return zeroPadding; + } + + /** + * return true or false, indicates that the prefix-omitted, padded object + * identifier should be reversed + */ + public boolean reverseObjectRoot() { + return reverseObjectRoot; + } + + /** + * The case-insensitive, delimiter marking the end of the OCFL object + * identifier prefix; MUST consist of a character string of length one or + * greater. If the delimiter is found multiple times in the OCFL object + * identifier, its last occurrence (right-most) will be used to select the + * termination of the prefix. + * + * @param delimiter + * marking the end of prefix + */ + public NTupleOmitPrefixStorageLayoutConfig setDelimiter(String delimiter) { + if (StringUtils.isBlank(delimiter)) { + throw new IllegalArgumentException("Arg must not be empty or null: 'delimiter'"); + } + this.delimiter = delimiter; + return this; + } + + /** + * the segment size (in characters) to split the digest into + * + * @param tupleSize + * - the segment size (in characters) to split the digest into + * + */ + public NTupleOmitPrefixStorageLayoutConfig setTupleSize(int tupleSize) { + if (tupleSize <= 0) { + throw new IllegalArgumentException("Arg must not be less than 1: 'tupleSize'. Given argument: " + tupleSize); + } + this.tupleSize = tupleSize; + return this; + } + + /** + * The number of segments to use for path generation + * + * @param numberOfTuples + * - The number of segments to use for path generation + * + */ + public NTupleOmitPrefixStorageLayoutConfig setNumberOfTuples(int numberOfTuples) { + if (numberOfTuples <= 0) { + throw new IllegalArgumentException("Arg must not be less than 1: 'numberOfTuples'. Given argument: " + numberOfTuples); + } + this.numberOfTuples = numberOfTuples; + return this; + } + + /** + * Indicates whether to use left or right zero padding for ids less than + * tupleSize * numberOfTuples + * + * @param zeroPadding + * + */ + public NTupleOmitPrefixStorageLayoutConfig setZeroPadding(String zeroPadding) { + if (!zeroPadding.equals(ZERO_PADDING_LEFT) && !zeroPadding.equals(ZERO_PADDING_RIGHT) && !zeroPadding.equals(ZERO_PADDING_NONE)) { + throw new IllegalArgumentException("Arg must not 'left', 'right', or 'none': 'zeroPadding'. Given argument:" + zeroPadding); + } + this.zeroPadding = zeroPadding; + return this; + } + + /** + * indicates that the prefix-omitted, padded object identifier should be + * reversed + * + * @param reverseObjectRoot + * - indicates that the prefix-omitted, padded object identifier + * should be reversed + * + */ + public NTupleOmitPrefixStorageLayoutConfig setReverseObjectRoot(boolean reverseObjectRoot) { + this.reverseObjectRoot = reverseObjectRoot; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + NTupleOmitPrefixStorageLayoutConfig that = (NTupleOmitPrefixStorageLayoutConfig) o; + return delimiter.equals(that.delimiter) && tupleSize == that.tupleSize && numberOfTuples == that.numberOfTuples && zeroPadding == that.zeroPadding + && reverseObjectRoot == that.reverseObjectRoot; + } + + @Override + public int hashCode() { + return Objects.hash(delimiter); + } + + @Override + public String toString() { + return "NTupleOmitPrefixStorageLayoutConfig{ delimiter='" + delimiter + "', tupleSize='" + tupleSize + "', numberOfTuples='" + numberOfTuples + + "', zeroPadding='" + zeroPadding + "', reverseObjectRoot='" + reverseObjectRoot + "' }"; + } + +} \ No newline at end of file diff --git a/ocfl-java-core/src/test/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageExtensionTest.java b/ocfl-java-core/src/test/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageExtensionTest.java new file mode 100644 index 00000000..97c1bc8f --- /dev/null +++ b/ocfl-java-core/src/test/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageExtensionTest.java @@ -0,0 +1,252 @@ +/** + Copyright @2021 President and Fellows of Harvard College + + 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 edu.wisc.library.ocfl.core.extension.storage.layout; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import edu.wisc.library.ocfl.core.extension.storage.layout.NTupleOmitPrefixStorageLayoutExtension; +import edu.wisc.library.ocfl.core.extension.storage.layout.config.NTupleOmitPrefixStorageLayoutConfig; + +/** + * @author vcrema + * @since 2021-10-25 + */ +public class NTupleOmitPrefixStorageExtensionTest { + + private NTupleOmitPrefixStorageLayoutExtension ext; + private NTupleOmitPrefixStorageLayoutConfig config; + + @BeforeEach + public void setUp() { + ext = new NTupleOmitPrefixStorageLayoutExtension(); + config = new NTupleOmitPrefixStorageLayoutConfig(); + } + + @Test + public void testNullDelimiter() { + assertThrows(IllegalArgumentException.class, () -> config.setDelimiter(null), + "Expected IllegalArgumentException"); + } + + @Test + public void testEmptyDelimiter() { + assertThrows(IllegalArgumentException.class, () -> config.setDelimiter(""), + "Expected IllegalArgumentException"); + } + + @Test + public void testNegativeTupleSize() { + assertThrows(IllegalArgumentException.class, () -> config.setTupleSize(-1), + "Expected IllegalArgumentException"); + } + + @Test + public void testZeroTupleSize() { + assertThrows(IllegalArgumentException.class, () -> config.setTupleSize(0), + "Expected IllegalArgumentException"); + } + + @Test + public void testNegativeNumberOfTuples() { + assertThrows(IllegalArgumentException.class, () -> config.setNumberOfTuples(-1), + "Expected IllegalArgumentException"); + } + + @Test + public void testZeroNumberOfTuples() { + assertThrows(IllegalArgumentException.class, () -> config.setNumberOfTuples(0), + "Expected IllegalArgumentException"); + } + + @Test + public void testIncorrectZeroPadding() { + assertThrows(IllegalArgumentException.class, () -> config.setZeroPadding("some value"), + "Expected IllegalArgumentException"); + } + + @Test + public void testZeroPaddingNoneAndShortString() { + config.setDelimiter(":"); + config.setTupleSize(4); + config.setNumberOfTuples(2); + config.setZeroPadding(NTupleOmitPrefixStorageLayoutConfig.ZERO_PADDING_NONE); + ext.init(config); + //Defaults: + //reverseObjectRoot: false + + //String is too short for requested tuples so it will throw runtime exception + assertThrows(RuntimeException.class, () -> ext.mapObjectId("namespace:1288729"), + "Expected RuntimeException"); + } + + + @Test + public void testDelimiterNotFound() { + config.setDelimiter("/"); + ext.init(config); + //Defaults: + //tupleSize: 3 + //numberOfTuples: 3, + //zeroPadding: "left", + //reverseObjectRoot: false + + assertThrows(RuntimeException.class, () -> ext.mapObjectId("namespace:12887296"), + "Expected RuntimeException"); + } + + @Test + public void testOneOccurrenceOfSingleCharDelimiter() { + config.setDelimiter(":"); + ext.init(config); + //Defaults: + //tupleSize: 3 + //numberOfTuples: 3, + //zeroPadding: "left", + //reverseObjectRoot: false + + String result = ext.mapObjectId("namespace:128872961"); + assertEquals("128/872/961/128872961", result); + } + + @Test + public void testTwoOccurrencesOfSingleCharDelimiter() { + config.setDelimiter(":"); + ext.init(config); + //Defaults: + //tupleSize: 3 + //numberOfTuples: 3, + //zeroPadding: "left", + //reverseObjectRoot: false + + String result = ext.mapObjectId("urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66"); + assertEquals("6e8/bc4/30-/6e8bc430-9c3a-11d9-9669-0800200c9a66", result); + } + + @Test + public void testTwoOccurrencesOfSingleCharDelimiterDRS() { + config.setDelimiter(":"); + ext.init(config); + //Defaults: + //tupleSize: 3 + //numberOfTuples: 3, + //zeroPadding: "left", + //reverseObjectRoot: false + + String result = ext.mapObjectId("urn-3:HUL.DRS.OBJECT:128872961"); + assertEquals("128/872/961/128872961", result); + } + + @Test + public void testOneOccurrenceOfMultiCharDelimiter() { + config.setDelimiter("edu/"); + ext.init(config); + //Defaults: + //tupleSize: 3 + //numberOfTuples: 3, + //zeroPadding: "left", + //reverseObjectRoot: false + + String result = ext.mapObjectId("https://institution.edu/344879388"); + assertEquals("344/879/388/344879388", result); + } + + @Test + public void testTwoOccurrencesOfMultiCharDelimiter() { + config.setDelimiter("edu/"); + ext.init(config); + //Defaults: + //tupleSize: 3 + //numberOfTuples: 3, + //zeroPadding: "left", + //reverseObjectRoot: false + + String result = ext.mapObjectId("https://institution.edu/abc/edu/f8.05v"); + assertEquals("000/f8./05v/f8.05v", result); + } + + @Test + public void testTupleAndLeftPadding() { + config.setDelimiter(":"); + config.setTupleSize(4); + config.setNumberOfTuples(2); + ext.init(config); + //Defaults: + //zeroPadding: "left", + //reverseObjectRoot: false + + String result = ext.mapObjectId("namespace:1288729"); + assertEquals("0128/8729/1288729", result); + } + + @Test + public void testTupleAndRightPadding() { + config.setDelimiter(":"); + config.setTupleSize(4); + config.setNumberOfTuples(2); + config.setZeroPadding(NTupleOmitPrefixStorageLayoutConfig.ZERO_PADDING_RIGHT); + ext.init(config); + //Defaults: + //reverseObjectRoot: false + + String result = ext.mapObjectId("namespace:1288729"); + assertEquals("1288/7290/1288729", result); + } + + @Test + public void testReverseAndLeftPadding() { + config.setDelimiter(":"); + config.setTupleSize(4); + config.setNumberOfTuples(2); + config.setReverseObjectRoot(true); + config.setZeroPadding("right"); + ext.init(config); + + String result = ext.mapObjectId("namespace:1288729"); + assertEquals("9278/8210/1288729", result); + } + + @Test + public void testOneOccurrenceOfMultiCharDelimiterAtEnd() { + config.setDelimiter("edu/"); + ext.init(config); + //Defaults: + //tupleSize: 3 + //numberOfTuples: 3, + //zeroPadding: "left", + //reverseObjectRoot: false + + assertThrows(RuntimeException.class, () -> ext.mapObjectId("https://institution.edu/"), + "Expected RuntimeException"); + } + + @Test + public void testMultiCharDelimiterNotFound() { + config.setDelimiter("com/"); + ext.init(config); + //Defaults: + //tupleSize: 3 + //numberOfTuples: 3, + //zeroPadding: "left", + //reverseObjectRoot: false + + assertThrows(RuntimeException.class, () -> ext.mapObjectId("https://institution.edu/344879388"), + "Expected RuntimeException"); + } +} From 72e80445b7278437855b0149e273074a3c606879 Mon Sep 17 00:00:00 2001 From: vac765 Date: Mon, 13 Dec 2021 13:16:44 -0500 Subject: [PATCH 2/8] pom updates --- ocfl-java-core/pom.xml | 4 ++++ pom.xml | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/ocfl-java-core/pom.xml b/ocfl-java-core/pom.xml index 766fd24a..6f82014e 100644 --- a/ocfl-java-core/pom.xml +++ b/ocfl-java-core/pom.xml @@ -122,6 +122,10 @@ net.jodah failsafe + + org.apache.commons + commons-lang3 + diff --git a/pom.xml b/pom.xml index 27cec3c2..96217c68 100644 --- a/pom.xml +++ b/pom.xml @@ -351,6 +351,12 @@ bcprov-jdk15on 1.69 + + org.apache.commons + commons-lang3 + 3.12.0 + + @@ -367,7 +373,7 @@ hamcrest-library 2.2 - + org.junit.jupiter junit-jupiter 5.8.1 From cdc529c93958156910d58047103ca3603cb0a95b Mon Sep 17 00:00:00 2001 From: vac765 Date: Mon, 13 Dec 2021 13:18:00 -0500 Subject: [PATCH 3/8] Readme update --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 484d1047..7685dcb2 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ following extensions are implemented: * [0003-hash-and-id-n-tuple-storage-layout](https://ocfl.github.io/extensions/0003-hash-and-id-n-tuple-storage-layout.html): `HashedNTupleIdEncapsulationLayoutConfig` * [0004-hashed-n-tuple-storage-layout](https://ocfl.github.io/extensions/0004-hashed-n-tuple-storage-layout.html): `HashedNTupleLayoutConfig` * [0006-flat-omit-prefix-storage-layout](https://ocfl.github.io/extensions/0006-flat-omit-prefix-storage-layout.html): `FlatOmitPrefixLayoutConfig` + * [0007-n-tuple-omit-prefix-storage-layout](https://ocfl.github.io/extensions/0007-n-tuple-omit-prefix-storage-layout.html): `NTupleOmitPrefixStorageLayoutConfig` ### Optional Properties @@ -397,6 +398,9 @@ extensions: * [0006-flat-omit-prefix-storage-layout](https://ocfl.github.io/extensions/0006-flat-omit-prefix-storage-layout.html) * Configuration class: `FlatOmitPrefixLayoutConfig` * Implementation class: `FlatOmitPrefixLayoutExtension` +* [0007-n-tuple-omit-prefix-storage-layout](https://ocfl.github.io/extensions/0007-n-tuple-omit-prefix-storage-layout.html) + * Configuration class: `NTupleOmitPrefixStorageLayoutConfig` + * Implementation class: `NTupleOmitPrefixStorageLayoutExtension` #### Custom Storage Layout Extensions From ec51e7cdf83a3de19ae38c78f46eb43310a00337 Mon Sep 17 00:00:00 2001 From: vac765 Date: Tue, 14 Dec 2021 10:04:43 -0500 Subject: [PATCH 4/8] licensing --- ...TupleOmitPrefixStorageLayoutExtension.java | 36 +++++++++++-------- .../NTupleOmitPrefixStorageLayoutConfig.java | 23 ++++++++++++ .../NTupleOmitPrefixStorageExtensionTest.java | 36 +++++++++++-------- 3 files changed, 67 insertions(+), 28 deletions(-) diff --git a/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageLayoutExtension.java b/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageLayoutExtension.java index 2c443ff7..702aadd5 100644 --- a/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageLayoutExtension.java +++ b/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageLayoutExtension.java @@ -1,17 +1,25 @@ -/** - Copyright @2021 President and Fellows of Harvard College - - 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. +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 University of Wisconsin Board of Regents + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package edu.wisc.library.ocfl.core.extension.storage.layout; diff --git a/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/config/NTupleOmitPrefixStorageLayoutConfig.java b/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/config/NTupleOmitPrefixStorageLayoutConfig.java index 1e7ef014..9a9756b6 100644 --- a/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/config/NTupleOmitPrefixStorageLayoutConfig.java +++ b/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/config/NTupleOmitPrefixStorageLayoutConfig.java @@ -1,3 +1,26 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 University of Wisconsin Board of Regents + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package edu.wisc.library.ocfl.core.extension.storage.layout.config; import java.util.Objects; diff --git a/ocfl-java-core/src/test/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageExtensionTest.java b/ocfl-java-core/src/test/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageExtensionTest.java index 97c1bc8f..b09d26da 100644 --- a/ocfl-java-core/src/test/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageExtensionTest.java +++ b/ocfl-java-core/src/test/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageExtensionTest.java @@ -1,17 +1,25 @@ -/** - Copyright @2021 President and Fellows of Harvard College - - 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. +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 University of Wisconsin Board of Regents + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ package edu.wisc.library.ocfl.core.extension.storage.layout; From 72bc850d0a01dd15463fd0d3faa67a3f3115da06 Mon Sep 17 00:00:00 2001 From: vac765 Date: Tue, 14 Dec 2021 11:58:04 -0500 Subject: [PATCH 5/8] dependency removal --- ocfl-java-core/pom.xml | 4 ---- pom.xml | 7 ------- 2 files changed, 11 deletions(-) diff --git a/ocfl-java-core/pom.xml b/ocfl-java-core/pom.xml index 6f82014e..766fd24a 100644 --- a/ocfl-java-core/pom.xml +++ b/ocfl-java-core/pom.xml @@ -122,10 +122,6 @@ net.jodah failsafe - - org.apache.commons - commons-lang3 - diff --git a/pom.xml b/pom.xml index 96217c68..5126c9ec 100644 --- a/pom.xml +++ b/pom.xml @@ -351,13 +351,6 @@ bcprov-jdk15on 1.69 - - org.apache.commons - commons-lang3 - 3.12.0 - - - software.amazon.awssdk From f8671d6a8a8379e5d8cc65c9a0b0599fa4003f77 Mon Sep 17 00:00:00 2001 From: vac765 Date: Tue, 14 Dec 2021 12:11:57 -0500 Subject: [PATCH 6/8] PR corrections --- ...TupleOmitPrefixStorageLayoutExtension.java | 100 +++++++++--------- .../NTupleOmitPrefixStorageLayoutConfig.java | 42 +++----- .../NTupleOmitPrefixStorageExtensionTest.java | 63 +++++------ 3 files changed, 98 insertions(+), 107 deletions(-) diff --git a/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageLayoutExtension.java b/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageLayoutExtension.java index 702aadd5..d2815894 100644 --- a/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageLayoutExtension.java +++ b/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageLayoutExtension.java @@ -23,11 +23,13 @@ */ package edu.wisc.library.ocfl.core.extension.storage.layout; -import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import edu.wisc.library.ocfl.core.extension.storage.layout.config.NTupleOmitPrefixStorageLayoutConfig; +import edu.wisc.library.ocfl.api.OcflConstants; +import edu.wisc.library.ocfl.api.exception.OcflExtensionException; +import edu.wisc.library.ocfl.api.util.Enforce; import edu.wisc.library.ocfl.core.extension.OcflExtensionConfig; import edu.wisc.library.ocfl.core.extension.storage.layout.OcflStorageLayoutExtension; @@ -58,7 +60,20 @@ public String getExtensionName() { */ @Override public String getDescription() { - return "This storage root extension describes an OCFL storage layout combining a pairtree-like root directory structure derived from prefix-omitted object identifiers, followed by the prefix-omitted object identifier themselves. The OCFL object identifiers are expected to contain prefixes which are removed in the mapping to directory names. The OCFL object identifier prefix is defined as all characters before and including a configurable delimiter. Where the prefix-omitted identifier length is less than tuple size * number of tuples, the remaining object id (prefix omitted) is left or right-side, zero-padded (configurable, left default), or not padded (none), and optionally reversed (default false). The object id is then divided into N n-tuple segments, and used to create nested paths under the OCFL storage root, followed by the prefix-omitted object id directory."; + return "This storage root extension describes an OCFL storage layout " + + "combining a pairtree-like root directory structure derived from " + + "prefix-omitted object identifiers, followed by the prefix-omitted " + + "object identifier themselves. The OCFL object identifiers are " + + "expected to contain prefixes which are removed in the mapping to " + + "directory names. The OCFL object identifier prefix is defined as " + + "all characters before and including a configurable delimiter. " + + "Where the prefix-omitted identifier length is less than " + + "tuple size * number of tuples, the remaining object id (prefix omitted) " + + "is left or right-side, zero-padded (configurable, left default), " + + "or not padded (none), and optionally reversed (default false). " + + "The object id is then divided into N n-tuple segments, and used " + + "to create nested paths under the OCFL storage root, followed by " + + "the prefix-omitted object id directory."; } /** @@ -66,44 +81,18 @@ public String getDescription() { */ @Override public synchronized void init(OcflExtensionConfig config) { - // Only set this.config if it is uninitialized - if (this.config == null) { - - // Is arg config null? - if (config == null) { - throw new IllegalArgumentException("Arg config must not be null!"); - } - - if (!(config instanceof NTupleOmitPrefixStorageLayoutConfig)) { - throw new IllegalArgumentException(String.format("This extension only supports %s configuration. Received: %s", - getExtensionConfigClass(), config)); - } + Enforce.notNull(config, "configFile cannot be null"); + + if (!(config instanceof NTupleOmitPrefixStorageLayoutConfig)) { + throw new OcflExtensionException(String.format("This extension only supports %s configuration. Received: %s", + getExtensionConfigClass(), config)); + } - NTupleOmitPrefixStorageLayoutConfig castConfig = (NTupleOmitPrefixStorageLayoutConfig) config; + NTupleOmitPrefixStorageLayoutConfig castConfig = (NTupleOmitPrefixStorageLayoutConfig) config; - validateConfig(castConfig); - this.config = castConfig; - } + this.config = castConfig; } - private static void validateConfig(NTupleOmitPrefixStorageLayoutConfig config) { - if (config != null) { - if (StringUtils.isBlank(config.getDelimiter())) { - throw new RuntimeException("Delimiter configuration must not be empty!"); - } - if (config.getTupleSize() <= 0) { - throw new RuntimeException("Character count configuration must not less than 0! Value given:" + config.getTupleSize()); - } - if (config.getNumberOfTuples() <= 0) { - throw new RuntimeException("Number of tuples configuration must not less than 0! Value given: " + config.getNumberOfTuples()); - } - if (!config.getZeroPadding().equals(NTupleOmitPrefixStorageLayoutConfig.ZERO_PADDING_LEFT) - && !config.getZeroPadding().equals(NTupleOmitPrefixStorageLayoutConfig.ZERO_PADDING_RIGHT) - && !config.getZeroPadding().equals(NTupleOmitPrefixStorageLayoutConfig.ZERO_PADDING_NONE)) { - throw new RuntimeException("Arg must not 'left', 'right', or 'none': 'zeroPadding'. Value given: " + config.getZeroPadding()); - } - } - } @Override public Class getExtensionConfigClass() { @@ -116,17 +105,32 @@ public Class getExtensionConfigClass() { @Override public String mapObjectId(String objectId) { if (config == null) { - throw new RuntimeException("This extension must be initialized before it can be used."); + throw new OcflExtensionException("This extension must be initialized before it can be used."); } - if (!objectId.contains(config.getDelimiter())) { - throw new RuntimeException("The delimiter " + config.getDelimiter() + " cannot be found in " + objectId + "."); + + if (!objectId.matches("\\A\\p{ASCII}*\\z")) { + throw new OcflExtensionException(String.format("This id %s must contain only ASCII characters.", objectId)); } //Split by delimiter and get the last part - String[] parts = StringUtils. splitByWholeSeparator(objectId, config.getDelimiter()); - String section = parts[parts.length - 1]; + String id = objectId.toLowerCase(); + int index = id.lastIndexOf(config.getDelimiter()); + String section = objectId; + String baseObjectId = ""; + + if (index > -1) { + section = objectId.substring(index + config.getDelimiter().length()); + baseObjectId = section; + } + else { + throw new OcflExtensionException(String.format( "The delimiter %s cannot be found in %s.", config.getDelimiter(), objectId)); + } + if (OcflConstants.EXTENSIONS_DIR.equals(section) || section.isEmpty()) { + throw new OcflExtensionException(String.format("The object id <%s> is incompatible with layout extension " + + "%s because it is empty or conflicts with the extensions directory.", objectId, EXTENSION_NAME)); + } if (section.length() == 0) { - throw new RuntimeException("The delimiter " + config.getDelimiter() + " is only found at the end of " + objectId + "."); + throw new OcflExtensionException(String.format("The delimiter %s is only found at the end of %s.", config.getDelimiter(), objectId)); } if (config.reverseObjectRoot()) { @@ -137,15 +141,15 @@ public String mapObjectId(String objectId) { if (section.length() < config.getTupleSize() * config.getNumberOfTuples()) { int paddingAmount = config.getTupleSize() * config.getNumberOfTuples(); - if (config.getZeroPadding().equals(NTupleOmitPrefixStorageLayoutConfig.ZERO_PADDING_LEFT)) { - section = StringUtils.leftPad(section, paddingAmount, "0"); + if (config.getZeroPadding().equals(NTupleOmitPrefixStorageLayoutConfig.ZeroPadding.LEFT)) { + section = "0".repeat(paddingAmount-section.length()) + section; } - else if (config.getZeroPadding().equals(NTupleOmitPrefixStorageLayoutConfig.ZERO_PADDING_RIGHT)) { - section = StringUtils.rightPad(section, paddingAmount, "0"); + else if (config.getZeroPadding().equals(NTupleOmitPrefixStorageLayoutConfig.ZeroPadding.RIGHT)) { + section = section + "0".repeat(paddingAmount-section.length()); } //Throw runtime exception since we can't pad and there won't be enough characters for the pattern else { - throw new RuntimeException("Zero padding is set to 'none' but " + section + " is too short to follow the requested tuple pattern: " + config.toString()); + throw new OcflExtensionException(String.format("Zero padding is set to 'none' but %s is too short to follow the requested tuple pattern: %s.", section, config.toString())); } } StringBuilder pathBuilder = new StringBuilder(); @@ -157,7 +161,7 @@ else if (config.getZeroPadding().equals(NTupleOmitPrefixStorageLayoutConfig.ZERO } //Append the original object id after the delimiter - pathBuilder.append(parts[parts.length - 1]); + pathBuilder.append(baseObjectId); return pathBuilder.toString(); } diff --git a/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/config/NTupleOmitPrefixStorageLayoutConfig.java b/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/config/NTupleOmitPrefixStorageLayoutConfig.java index 9a9756b6..5067cfab 100644 --- a/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/config/NTupleOmitPrefixStorageLayoutConfig.java +++ b/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/config/NTupleOmitPrefixStorageLayoutConfig.java @@ -27,9 +27,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; -import org.apache.commons.lang3.StringUtils; - import edu.wisc.library.ocfl.core.extension.storage.layout.NTupleOmitPrefixStorageLayoutExtension; +import edu.wisc.library.ocfl.api.util.Enforce; import edu.wisc.library.ocfl.core.extension.OcflExtensionConfig; /** @@ -41,14 +40,15 @@ */ public class NTupleOmitPrefixStorageLayoutConfig implements OcflExtensionConfig { - public static final String ZERO_PADDING_LEFT = "left"; - public static final String ZERO_PADDING_RIGHT = "right"; - public static final String ZERO_PADDING_NONE = "none"; - + public enum ZeroPadding { + LEFT, + RIGHT + } + private String delimiter; private int tupleSize = 3; private int numberOfTuples = 3; - private String zeroPadding = ZERO_PADDING_LEFT; + private ZeroPadding zeroPadding = ZeroPadding.LEFT; private boolean reverseObjectRoot = false; @JsonIgnore @@ -89,7 +89,7 @@ public int getNumberOfTuples() { * return zeroPadding - Indicates whether to use left or right zero padding * for ids less than tupleSize * numberOfTuples */ - public String getZeroPadding() { + public ZeroPadding getZeroPadding() { return zeroPadding; } @@ -112,10 +112,8 @@ public boolean reverseObjectRoot() { * marking the end of prefix */ public NTupleOmitPrefixStorageLayoutConfig setDelimiter(String delimiter) { - if (StringUtils.isBlank(delimiter)) { - throw new IllegalArgumentException("Arg must not be empty or null: 'delimiter'"); - } - this.delimiter = delimiter; + this.delimiter = Enforce.expressionTrue(delimiter != null && !delimiter.isEmpty(), delimiter, "delimiter must not be empty"); + return this; } @@ -127,10 +125,8 @@ public NTupleOmitPrefixStorageLayoutConfig setDelimiter(String delimiter) { * */ public NTupleOmitPrefixStorageLayoutConfig setTupleSize(int tupleSize) { - if (tupleSize <= 0) { - throw new IllegalArgumentException("Arg must not be less than 1: 'tupleSize'. Given argument: " + tupleSize); - } - this.tupleSize = tupleSize; + this.tupleSize = Enforce.expressionTrue(tupleSize >=1 && tupleSize <= 32, + tupleSize, "tupleSize must be between 1 and 32 inclusive"); return this; } @@ -142,10 +138,8 @@ public NTupleOmitPrefixStorageLayoutConfig setTupleSize(int tupleSize) { * */ public NTupleOmitPrefixStorageLayoutConfig setNumberOfTuples(int numberOfTuples) { - if (numberOfTuples <= 0) { - throw new IllegalArgumentException("Arg must not be less than 1: 'numberOfTuples'. Given argument: " + numberOfTuples); - } - this.numberOfTuples = numberOfTuples; + this.numberOfTuples= Enforce.expressionTrue(numberOfTuples>=1 && numberOfTuples<= 32, + numberOfTuples, "numberOfTuples must be between 1 and 32 inclusive"); return this; } @@ -156,10 +150,8 @@ public NTupleOmitPrefixStorageLayoutConfig setNumberOfTuples(int numberOfTuples) * @param zeroPadding * */ - public NTupleOmitPrefixStorageLayoutConfig setZeroPadding(String zeroPadding) { - if (!zeroPadding.equals(ZERO_PADDING_LEFT) && !zeroPadding.equals(ZERO_PADDING_RIGHT) && !zeroPadding.equals(ZERO_PADDING_NONE)) { - throw new IllegalArgumentException("Arg must not 'left', 'right', or 'none': 'zeroPadding'. Given argument:" + zeroPadding); - } + public NTupleOmitPrefixStorageLayoutConfig setZeroPadding(ZeroPadding zeroPadding) { + Enforce.notNull(zeroPadding, "Zero padding cannot be null"); this.zeroPadding = zeroPadding; return this; } @@ -191,7 +183,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(delimiter); + return Objects.hash(delimiter, tupleSize, numberOfTuples, reverseObjectRoot, zeroPadding); } @Override diff --git a/ocfl-java-core/src/test/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageExtensionTest.java b/ocfl-java-core/src/test/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageExtensionTest.java index b09d26da..d24fb439 100644 --- a/ocfl-java-core/src/test/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageExtensionTest.java +++ b/ocfl-java-core/src/test/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageExtensionTest.java @@ -29,6 +29,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import edu.wisc.library.ocfl.api.exception.OcflExtensionException; +import edu.wisc.library.ocfl.api.exception.OcflInputException; import edu.wisc.library.ocfl.core.extension.storage.layout.NTupleOmitPrefixStorageLayoutExtension; import edu.wisc.library.ocfl.core.extension.storage.layout.config.NTupleOmitPrefixStorageLayoutConfig; @@ -49,59 +51,52 @@ public void setUp() { @Test public void testNullDelimiter() { - assertThrows(IllegalArgumentException.class, () -> config.setDelimiter(null), - "Expected IllegalArgumentException"); + assertThrows(OcflInputException.class, () -> config.setDelimiter(null), + "Expected OcflInputException"); } @Test public void testEmptyDelimiter() { - assertThrows(IllegalArgumentException.class, () -> config.setDelimiter(""), - "Expected IllegalArgumentException"); + assertThrows(OcflInputException.class, () -> config.setDelimiter(""), + "Expected OcflInputException"); } @Test public void testNegativeTupleSize() { - assertThrows(IllegalArgumentException.class, () -> config.setTupleSize(-1), - "Expected IllegalArgumentException"); + assertThrows(OcflInputException.class, () -> config.setTupleSize(-1), + "Expected OcflInputException"); } @Test public void testZeroTupleSize() { - assertThrows(IllegalArgumentException.class, () -> config.setTupleSize(0), - "Expected IllegalArgumentException"); + assertThrows(OcflInputException.class, () -> config.setTupleSize(0), + "Expected OcflInputException"); } @Test public void testNegativeNumberOfTuples() { - assertThrows(IllegalArgumentException.class, () -> config.setNumberOfTuples(-1), - "Expected IllegalArgumentException"); + assertThrows(OcflInputException.class, () -> config.setNumberOfTuples(-1), + "Expected OcflInputException"); } @Test public void testZeroNumberOfTuples() { - assertThrows(IllegalArgumentException.class, () -> config.setNumberOfTuples(0), - "Expected IllegalArgumentException"); + assertThrows(OcflInputException.class, () -> config.setNumberOfTuples(0), + "Expected OcflInputException"); } @Test public void testIncorrectZeroPadding() { - assertThrows(IllegalArgumentException.class, () -> config.setZeroPadding("some value"), - "Expected IllegalArgumentException"); + assertThrows(OcflInputException.class, () -> config.setZeroPadding(null), + "Expected OcflInputException"); } @Test - public void testZeroPaddingNoneAndShortString() { - config.setDelimiter(":"); - config.setTupleSize(4); - config.setNumberOfTuples(2); - config.setZeroPadding(NTupleOmitPrefixStorageLayoutConfig.ZERO_PADDING_NONE); + public void testNonAsciiId() { + config.setDelimiter("/"); ext.init(config); - //Defaults: - //reverseObjectRoot: false - - //String is too short for requested tuples so it will throw runtime exception - assertThrows(RuntimeException.class, () -> ext.mapObjectId("namespace:1288729"), - "Expected RuntimeException"); + assertThrows(OcflExtensionException.class, () -> ext.mapObjectId("jå∫∆a/vµa2bl√øog"), + "Expected OcflExtensionException"); } @@ -115,8 +110,8 @@ public void testDelimiterNotFound() { //zeroPadding: "left", //reverseObjectRoot: false - assertThrows(RuntimeException.class, () -> ext.mapObjectId("namespace:12887296"), - "Expected RuntimeException"); + assertThrows(OcflExtensionException.class, () -> ext.mapObjectId("namespace:12887296"), + "Expected OcflExtensionException"); } @Test @@ -208,7 +203,7 @@ public void testTupleAndRightPadding() { config.setDelimiter(":"); config.setTupleSize(4); config.setNumberOfTuples(2); - config.setZeroPadding(NTupleOmitPrefixStorageLayoutConfig.ZERO_PADDING_RIGHT); + config.setZeroPadding(NTupleOmitPrefixStorageLayoutConfig.ZeroPadding.RIGHT); ext.init(config); //Defaults: //reverseObjectRoot: false @@ -218,12 +213,12 @@ public void testTupleAndRightPadding() { } @Test - public void testReverseAndLeftPadding() { + public void testReverseAndRightPadding() { config.setDelimiter(":"); config.setTupleSize(4); config.setNumberOfTuples(2); config.setReverseObjectRoot(true); - config.setZeroPadding("right"); + config.setZeroPadding(NTupleOmitPrefixStorageLayoutConfig.ZeroPadding.RIGHT); ext.init(config); String result = ext.mapObjectId("namespace:1288729"); @@ -240,8 +235,8 @@ public void testOneOccurrenceOfMultiCharDelimiterAtEnd() { //zeroPadding: "left", //reverseObjectRoot: false - assertThrows(RuntimeException.class, () -> ext.mapObjectId("https://institution.edu/"), - "Expected RuntimeException"); + assertThrows(OcflExtensionException.class, () -> ext.mapObjectId("https://institution.edu/"), + "Expected OcflExtensionException"); } @Test @@ -254,7 +249,7 @@ public void testMultiCharDelimiterNotFound() { //zeroPadding: "left", //reverseObjectRoot: false - assertThrows(RuntimeException.class, () -> ext.mapObjectId("https://institution.edu/344879388"), - "Expected RuntimeException"); + assertThrows(OcflExtensionException.class, () -> ext.mapObjectId("https://institution.edu/344879388"), + "Expected OcflExtensionException"); } } From 30f6b3de869770c9391291780da0e64dd046e9ed Mon Sep 17 00:00:00 2001 From: vac765 Date: Tue, 25 Jan 2022 10:50:52 -0500 Subject: [PATCH 7/8] Formatting corrections --- ocfl-java-api/pom.xml | 4 +- ocfl-java-aws/pom.xml | 8 +- ocfl-java-core/pom.xml | 8 +- ...TupleOmitPrefixStorageLayoutExtension.java | 95 +++--- .../NTupleOmitPrefixStorageLayoutConfig.java | 306 +++++++++--------- ...0007-n-tuple-omit-prefix-storage-layout.md | 183 +++++++++++ ocfl-java-itest/pom.xml | 10 +- ocfl-java-test/pom.xml | 6 +- pom.xml | 2 +- 9 files changed, 398 insertions(+), 224 deletions(-) create mode 100644 ocfl-java-core/src/main/resources/ocfl-specs/0007-n-tuple-omit-prefix-storage-layout.md diff --git a/ocfl-java-api/pom.xml b/ocfl-java-api/pom.xml index 2851142c..58edc825 100644 --- a/ocfl-java-api/pom.xml +++ b/ocfl-java-api/pom.xml @@ -31,12 +31,12 @@ edu.wisc.library.ocfl ocfl-java-parent - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT ../pom.xml ocfl-java-api - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT OCFL Java API Java API for interacting with objects stored in OCFL. diff --git a/ocfl-java-aws/pom.xml b/ocfl-java-aws/pom.xml index 27dcc8af..9a73fd56 100644 --- a/ocfl-java-aws/pom.xml +++ b/ocfl-java-aws/pom.xml @@ -31,12 +31,12 @@ edu.wisc.library.ocfl ocfl-java-parent - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT ../pom.xml ocfl-java-aws - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT OCFL Java AWS Java OCFL implementation that writes to S3. @@ -81,7 +81,7 @@ edu.wisc.library.ocfl ocfl-java-core - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT @@ -112,7 +112,7 @@ edu.wisc.library.ocfl ocfl-java-test - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT test diff --git a/ocfl-java-core/pom.xml b/ocfl-java-core/pom.xml index 766fd24a..0fe31f95 100644 --- a/ocfl-java-core/pom.xml +++ b/ocfl-java-core/pom.xml @@ -31,12 +31,12 @@ edu.wisc.library.ocfl ocfl-java-parent - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT ../pom.xml ocfl-java-core - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT OCFL Java Core Core Java OCFL implementation code. @@ -74,7 +74,7 @@ edu.wisc.library.ocfl ocfl-java-api - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT @@ -127,7 +127,7 @@ edu.wisc.library.ocfl ocfl-java-test - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT test diff --git a/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageLayoutExtension.java b/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageLayoutExtension.java index d2815894..3423136c 100644 --- a/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageLayoutExtension.java +++ b/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/NTupleOmitPrefixStorageLayoutExtension.java @@ -21,6 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package edu.wisc.library.ocfl.core.extension.storage.layout; import org.slf4j.Logger; @@ -34,7 +35,8 @@ import edu.wisc.library.ocfl.core.extension.storage.layout.OcflStorageLayoutExtension; /** - * Implementation of the + * Implementation of the * N Tuple Storage Layout extension. * * @author vcrema @@ -44,7 +46,7 @@ public class NTupleOmitPrefixStorageLayoutExtension implements OcflStorageLayout public static final String EXTENSION_NAME = "0007-n-tuple-omit-prefix-storage-layout"; private static final Logger LOG = LoggerFactory.getLogger(NTupleOmitPrefixStorageLayoutExtension.class); - + private NTupleOmitPrefixStorageLayoutConfig config; /** @@ -60,20 +62,14 @@ public String getExtensionName() { */ @Override public String getDescription() { - return "This storage root extension describes an OCFL storage layout " - + "combining a pairtree-like root directory structure derived from " - + "prefix-omitted object identifiers, followed by the prefix-omitted " - + "object identifier themselves. The OCFL object identifiers are " - + "expected to contain prefixes which are removed in the mapping to " - + "directory names. The OCFL object identifier prefix is defined as " - + "all characters before and including a configurable delimiter. " - + "Where the prefix-omitted identifier length is less than " - + "tuple size * number of tuples, the remaining object id (prefix omitted) " - + "is left or right-side, zero-padded (configurable, left default), " - + "or not padded (none), and optionally reversed (default false). " - + "The object id is then divided into N n-tuple segments, and used " - + "to create nested paths under the OCFL storage root, followed by " - + "the prefix-omitted object id directory."; + return "This storage root extension describes an OCFL storage layout " + "combining a pairtree-like root directory structure derived from " + + "prefix-omitted object identifiers, followed by the prefix-omitted " + "object identifier themselves. The OCFL object identifiers are " + + "expected to contain prefixes which are removed in the mapping to " + "directory names. The OCFL object identifier prefix is defined as " + + "all characters before and including a configurable delimiter. " + "Where the prefix-omitted identifier length is less than " + + "tuple size * number of tuples, the remaining object id (prefix omitted) " + + "is left or right-side, zero-padded (configurable, left default), " + "or not padded (none), and optionally reversed (default false). " + + "The object id is then divided into N n-tuple segments, and used " + "to create nested paths under the OCFL storage root, followed by " + + "the prefix-omitted object id directory."; } /** @@ -81,18 +77,16 @@ public String getDescription() { */ @Override public synchronized void init(OcflExtensionConfig config) { - Enforce.notNull(config, "configFile cannot be null"); - - if (!(config instanceof NTupleOmitPrefixStorageLayoutConfig)) { - throw new OcflExtensionException(String.format("This extension only supports %s configuration. Received: %s", - getExtensionConfigClass(), config)); - } + Enforce.notNull(config, "configFile cannot be null"); - NTupleOmitPrefixStorageLayoutConfig castConfig = (NTupleOmitPrefixStorageLayoutConfig) config; + if (!(config instanceof NTupleOmitPrefixStorageLayoutConfig)) { + throw new OcflExtensionException(String.format("This extension only supports %s configuration. Received: %s", getExtensionConfigClass(), config)); + } - this.config = castConfig; - } + NTupleOmitPrefixStorageLayoutConfig castConfig = (NTupleOmitPrefixStorageLayoutConfig) config; + this.config = castConfig; + } @Override public Class getExtensionConfigClass() { @@ -107,11 +101,11 @@ public String mapObjectId(String objectId) { if (config == null) { throw new OcflExtensionException("This extension must be initialized before it can be used."); } - + if (!objectId.matches("\\A\\p{ASCII}*\\z")) { - throw new OcflExtensionException(String.format("This id %s must contain only ASCII characters.", objectId)); + throw new OcflExtensionException(String.format("This id %s must contain only ASCII characters.", objectId)); } - //Split by delimiter and get the last part + // Split by delimiter and get the last part String id = objectId.toLowerCase(); int index = id.lastIndexOf(config.getDelimiter()); String section = objectId; @@ -120,47 +114,42 @@ public String mapObjectId(String objectId) { if (index > -1) { section = objectId.substring(index + config.getDelimiter().length()); baseObjectId = section; + } else { + throw new OcflExtensionException(String.format("The delimiter %s cannot be found in %s.", config.getDelimiter(), objectId)); } - else { - throw new OcflExtensionException(String.format( "The delimiter %s cannot be found in %s.", config.getDelimiter(), objectId)); - } - + if (OcflConstants.EXTENSIONS_DIR.equals(section) || section.isEmpty()) { - throw new OcflExtensionException(String.format("The object id <%s> is incompatible with layout extension " + - "%s because it is empty or conflicts with the extensions directory.", objectId, EXTENSION_NAME)); + throw new OcflExtensionException(String.format( + "The object id <%s> is incompatible with layout extension " + "%s because it is empty or conflicts with the extensions directory.", + objectId, EXTENSION_NAME)); } if (section.length() == 0) { - throw new OcflExtensionException(String.format("The delimiter %s is only found at the end of %s.", config.getDelimiter(), objectId)); + throw new OcflExtensionException(String.format("The delimiter %s is only found at the end of %s.", config.getDelimiter(), objectId)); } - + if (config.reverseObjectRoot()) { - //Reverse the section - section = new StringBuilder(section).reverse().toString(); + // Reverse the section + section = new StringBuilder(section).reverse().toString(); } - //Add padding if needed and requested + // Add padding if needed and requested if (section.length() < config.getTupleSize() * config.getNumberOfTuples()) { - - int paddingAmount = config.getTupleSize() * config.getNumberOfTuples(); - if (config.getZeroPadding().equals(NTupleOmitPrefixStorageLayoutConfig.ZeroPadding.LEFT)) { - section = "0".repeat(paddingAmount-section.length()) + section; - } - else if (config.getZeroPadding().equals(NTupleOmitPrefixStorageLayoutConfig.ZeroPadding.RIGHT)) { - section = section + "0".repeat(paddingAmount-section.length()); - } - //Throw runtime exception since we can't pad and there won't be enough characters for the pattern - else { - throw new OcflExtensionException(String.format("Zero padding is set to 'none' but %s is too short to follow the requested tuple pattern: %s.", section, config.toString())); - } + + int paddingAmount = config.getTupleSize() * config.getNumberOfTuples(); + if (config.getZeroPadding().equals(NTupleOmitPrefixStorageLayoutConfig.ZeroPadding.LEFT)) { + section = "0".repeat(paddingAmount - section.length()) + section; + } else if (config.getZeroPadding().equals(NTupleOmitPrefixStorageLayoutConfig.ZeroPadding.RIGHT)) { + section = section + "0".repeat(paddingAmount - section.length()); + } } StringBuilder pathBuilder = new StringBuilder(); - //Split into even sections + // Split into even sections for (int i = 0; i < config.getNumberOfTuples(); i++) { int start = i * config.getTupleSize(); int end = start + config.getTupleSize(); pathBuilder.append(section, start, end).append("/"); } - //Append the original object id after the delimiter + // Append the original object id after the delimiter pathBuilder.append(baseObjectId); return pathBuilder.toString(); } diff --git a/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/config/NTupleOmitPrefixStorageLayoutConfig.java b/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/config/NTupleOmitPrefixStorageLayoutConfig.java index 5067cfab..9632bdc8 100644 --- a/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/config/NTupleOmitPrefixStorageLayoutConfig.java +++ b/ocfl-java-core/src/main/java/edu/wisc/library/ocfl/core/extension/storage/layout/config/NTupleOmitPrefixStorageLayoutConfig.java @@ -21,6 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package edu.wisc.library.ocfl.core.extension.storage.layout.config; import java.util.Objects; @@ -32,7 +33,8 @@ import edu.wisc.library.ocfl.core.extension.OcflExtensionConfig; /** - * Configuration for the + * Configuration for the * N Tuple Storage Layout extension. * * @author vcrema @@ -40,156 +42,156 @@ */ public class NTupleOmitPrefixStorageLayoutConfig implements OcflExtensionConfig { - public enum ZeroPadding { - LEFT, - RIGHT - } - - private String delimiter; - private int tupleSize = 3; - private int numberOfTuples = 3; - private ZeroPadding zeroPadding = ZeroPadding.LEFT; - private boolean reverseObjectRoot = false; - - @JsonIgnore - @Override - public String getExtensionName() { - return NTupleOmitPrefixStorageLayoutExtension.EXTENSION_NAME; - } - - @JsonIgnore - @Override - public boolean hasParameters() { - return true; - } - - /** - * @return the delimiter marking end of prefix - */ - public String getDelimiter() { - return delimiter; - } - - /** - * @return tupleSize - the segment size (in characters) to split the digest - * into - */ - public int getTupleSize() { - return tupleSize; - } - - /** - * @return The number of segments to use for path generation - */ - public int getNumberOfTuples() { - return numberOfTuples; - } - - /** - * return zeroPadding - Indicates whether to use left or right zero padding - * for ids less than tupleSize * numberOfTuples - */ - public ZeroPadding getZeroPadding() { - return zeroPadding; - } - - /** - * return true or false, indicates that the prefix-omitted, padded object - * identifier should be reversed - */ - public boolean reverseObjectRoot() { - return reverseObjectRoot; - } - - /** - * The case-insensitive, delimiter marking the end of the OCFL object - * identifier prefix; MUST consist of a character string of length one or - * greater. If the delimiter is found multiple times in the OCFL object - * identifier, its last occurrence (right-most) will be used to select the - * termination of the prefix. - * - * @param delimiter - * marking the end of prefix - */ - public NTupleOmitPrefixStorageLayoutConfig setDelimiter(String delimiter) { - this.delimiter = Enforce.expressionTrue(delimiter != null && !delimiter.isEmpty(), delimiter, "delimiter must not be empty"); - - return this; - } - - /** - * the segment size (in characters) to split the digest into - * - * @param tupleSize - * - the segment size (in characters) to split the digest into - * - */ - public NTupleOmitPrefixStorageLayoutConfig setTupleSize(int tupleSize) { - this.tupleSize = Enforce.expressionTrue(tupleSize >=1 && tupleSize <= 32, - tupleSize, "tupleSize must be between 1 and 32 inclusive"); - return this; - } - - /** - * The number of segments to use for path generation - * - * @param numberOfTuples - * - The number of segments to use for path generation - * - */ - public NTupleOmitPrefixStorageLayoutConfig setNumberOfTuples(int numberOfTuples) { - this.numberOfTuples= Enforce.expressionTrue(numberOfTuples>=1 && numberOfTuples<= 32, - numberOfTuples, "numberOfTuples must be between 1 and 32 inclusive"); - return this; - } - - /** - * Indicates whether to use left or right zero padding for ids less than - * tupleSize * numberOfTuples - * - * @param zeroPadding - * - */ - public NTupleOmitPrefixStorageLayoutConfig setZeroPadding(ZeroPadding zeroPadding) { - Enforce.notNull(zeroPadding, "Zero padding cannot be null"); - this.zeroPadding = zeroPadding; - return this; - } - - /** - * indicates that the prefix-omitted, padded object identifier should be - * reversed - * - * @param reverseObjectRoot - * - indicates that the prefix-omitted, padded object identifier - * should be reversed - * - */ - public NTupleOmitPrefixStorageLayoutConfig setReverseObjectRoot(boolean reverseObjectRoot) { - this.reverseObjectRoot = reverseObjectRoot; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - NTupleOmitPrefixStorageLayoutConfig that = (NTupleOmitPrefixStorageLayoutConfig) o; - return delimiter.equals(that.delimiter) && tupleSize == that.tupleSize && numberOfTuples == that.numberOfTuples && zeroPadding == that.zeroPadding - && reverseObjectRoot == that.reverseObjectRoot; - } - - @Override - public int hashCode() { - return Objects.hash(delimiter, tupleSize, numberOfTuples, reverseObjectRoot, zeroPadding); - } - - @Override - public String toString() { - return "NTupleOmitPrefixStorageLayoutConfig{ delimiter='" + delimiter + "', tupleSize='" + tupleSize + "', numberOfTuples='" + numberOfTuples - + "', zeroPadding='" + zeroPadding + "', reverseObjectRoot='" + reverseObjectRoot + "' }"; - } + public enum ZeroPadding { + LEFT, RIGHT + } + + private String delimiter; + private int tupleSize = 3; + private int numberOfTuples = 3; + private ZeroPadding zeroPadding = ZeroPadding.LEFT; + private boolean reverseObjectRoot = false; + + @JsonIgnore + @Override + public String getExtensionName() { + return NTupleOmitPrefixStorageLayoutExtension.EXTENSION_NAME; + } + + @JsonIgnore + @Override + public boolean hasParameters() { + return true; + } + + /** + * @return the delimiter marking end of prefix + */ + public String getDelimiter() { + return delimiter; + } + + /** + * @return tupleSize - the segment size (in characters) to split the digest + * into + */ + public int getTupleSize() { + return tupleSize; + } + + /** + * @return The number of segments to use for path generation + */ + public int getNumberOfTuples() { + return numberOfTuples; + } + + /** + * return zeroPadding - Indicates whether to use left or right zero padding + * for ids less than tupleSize * numberOfTuples + */ + public ZeroPadding getZeroPadding() { + return zeroPadding; + } + + /** + * return true or false, indicates that the prefix-omitted, padded object + * identifier should be reversed + */ + public boolean reverseObjectRoot() { + return reverseObjectRoot; + } + + /** + * The case-insensitive, delimiter marking the end of the OCFL object + * identifier prefix; MUST consist of a character string of length one or + * greater. If the delimiter is found multiple times in the OCFL object + * identifier, its last occurrence (right-most) will be used to select the + * termination of the prefix. + * + * @param delimiter + * marking the end of prefix + */ + public NTupleOmitPrefixStorageLayoutConfig setDelimiter(String delimiter) { + this.delimiter = Enforce.expressionTrue(delimiter != null && !delimiter.isEmpty(), delimiter, "delimiter must not be empty"); + + return this; + } + + /** + * the segment size (in characters) to split the digest into + * + * @param tupleSize + * - the segment size (in characters) to split the digest into + * + */ + public NTupleOmitPrefixStorageLayoutConfig setTupleSize(int tupleSize) { + this.tupleSize = Enforce.expressionTrue(tupleSize >= 1 && tupleSize <= 32, tupleSize, "tupleSize must be between 1 and 32 inclusive"); + return this; + } + + /** + * The number of segments to use for path generation + * + * @param numberOfTuples + * - The number of segments to use for path generation + * + */ + public NTupleOmitPrefixStorageLayoutConfig setNumberOfTuples(int numberOfTuples) { + this.numberOfTuples = Enforce.expressionTrue(numberOfTuples >= 1 && numberOfTuples <= 32, numberOfTuples, + "numberOfTuples must be between 1 and 32 inclusive"); + return this; + } + + /** + * Indicates whether to use left or right zero padding for ids less than + * tupleSize * numberOfTuples + * + * @param zeroPadding + * + */ + public NTupleOmitPrefixStorageLayoutConfig setZeroPadding(ZeroPadding zeroPadding) { + Enforce.notNull(zeroPadding, "Zero padding cannot be null"); + this.zeroPadding = zeroPadding; + return this; + } + + /** + * indicates that the prefix-omitted, padded object identifier should be + * reversed + * + * @param reverseObjectRoot + * - indicates that the prefix-omitted, padded object identifier + * should be reversed + * + */ + public NTupleOmitPrefixStorageLayoutConfig setReverseObjectRoot(boolean reverseObjectRoot) { + this.reverseObjectRoot = reverseObjectRoot; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NTupleOmitPrefixStorageLayoutConfig that = (NTupleOmitPrefixStorageLayoutConfig) o; + return delimiter.equals(that.delimiter) && tupleSize == that.tupleSize && numberOfTuples == that.numberOfTuples && zeroPadding == that.zeroPadding + && reverseObjectRoot == that.reverseObjectRoot; + } + + @Override + public int hashCode() { + return Objects.hash(delimiter, tupleSize, numberOfTuples, reverseObjectRoot, zeroPadding); + } + + @Override + public String toString() { + return "NTupleOmitPrefixStorageLayoutConfig{ delimiter='" + delimiter + "', tupleSize='" + tupleSize + "', numberOfTuples='" + numberOfTuples + + "', zeroPadding='" + zeroPadding + "', reverseObjectRoot='" + reverseObjectRoot + "' }"; + } } \ No newline at end of file diff --git a/ocfl-java-core/src/main/resources/ocfl-specs/0007-n-tuple-omit-prefix-storage-layout.md b/ocfl-java-core/src/main/resources/ocfl-specs/0007-n-tuple-omit-prefix-storage-layout.md new file mode 100644 index 00000000..a7a7eb33 --- /dev/null +++ b/ocfl-java-core/src/main/resources/ocfl-specs/0007-n-tuple-omit-prefix-storage-layout.md @@ -0,0 +1,183 @@ +# OCFL Community Extension 0007: N Tuple Omit Prefix Storage Layout + + * **Extension Name:** 0007-n-tuple-omit-prefix-storage-layout + * **Authors:** Michael Vandermillen and Andrew Woods + * **Minimum OCFL Version:** 1.0 + * **OCFL Community Extensions Version:** 1.0 + * **Obsoletes:** n/a + * **Obsoleted by:** n/a + +## Overview + +This storage root extension describes an OCFL storage layout combining a pairtree-like root directory structure derived from prefix-omitted object identifiers, followed by the prefix-omitted object identifier themselves. +The OCFL object identifiers are expected to contain prefixes which are removed in the mapping to directory names. +The OCFL object identifier prefix is defined as all characters before and including a configurable delimiter. +Where the prefix-omitted identifier length is less than tuple size * number of tuples, the remaining object id (prefix omitted) is left or right-side, zero-padded (configurable, left default), and optionally reversed (default false). +The object id is then divided into N n-tuple segments, and used to create nested paths under the OCFL storage root, followed by the prefix-omitted object id directory. + +This layout combines the advantages of 0006-flat-omit-prefix-storage-layout (directory name transparency) and the 0004-hashed-n-tuple-storage-layout (enhanced file system/bucket performance). + +The limitations of this layout are filesystem dependent (with one exception), and are generally as follows: + +* The size of object identifiers, minus the length of the prefix, cannot exceed the maximum allowed directory name size + (eg. 255 characters) +* Object identifiers cannot include characters that are illegal in directory names +* This extension is defined over the ASCII subset of UTF-8 (code points 0x20 to 0x7F). Any character outside of this range in either an identifier or a path is an error. + +## Parameters + +### Summary + +* **Name:** `delimiter` + * **Description:** The case-insensitive, delimiter marking the end of the OCFL object identifier prefix; MUST consist + of a character string of length one or greater. If the delimiter is found multiple times in the OCFL object + identifier, its last occurence (right-most) will be used to select the termination of the prefix. + * **Type:** string + * **Constraints:** Must not be empty + * **Default:** : +* **Name**: `tupleSize` + * **Description:** Indicates the segment size (in characters) to split the + identifier into + * **Type:** number + * **Constraints:** An integer between 1 and 32 inclusive + * **Default:** 3 +* **Name:** `numberOfTuples` + * **Description:** Indicates the number of segments to use for path generation + * **Type:** number + * **Constraints:** An integer between 1 and 32 inclusive + * **Default:** 3 +* **Name:** `zeroPadding` + * **Description:** Indicates whether to use left or right zero padding for ids less than tupleSize * numberOfTuples + * **Type:** string + * **Constraints:** Must be either "left" or "right" + * **Default:** left +* **Name:** `reverseObjectRoot` + * **Description:** When true, indicates that the prefix-omitted, padded object identifier should be reversed + * **Type:** boolean + * **Default:** false + +## Procedure + +The following is an outline of the steps to map an OCFL object identifier to an +OCFL object root path: + +1. Remove the prefix, which is everything to the left of the right-most instance of the delimiter, as well as the delimiter. If there is no delimiter, the whole id is used; if the delimiter is found at the end, an error is thrown. +2. Optionally, add zero-padding to the left or right of the remaining id, depending on `zeroPadding` configuration. +3. Optionally reverse the remaining id, depending on `reverseObjectRoot` +4. Starting at the leftmost character of the resulting id and working right, divide the id into `numberOfTuples` each containing `tupleSize` characters. +5. Create the start of the object root path by joining the tuples, in order, using the filesystem path separator. +6. Complete the object root path by joining the prefix-omitted id (from step 1) onto the end. + +## Examples + +### Example 1 + +This example demonstrates mappings where the single-character delimiter is found one or more times in the OCFL object +identifier, with default `zeroPadding`, modified `tupleSize` and `numberOfTuples`, and `reverseObjectRoot` true. + +#### Parameters + +```json +{ + "extensionName": "0007-n-tuple-omit-prefix-storage-layout", + "delimiter": ":", + "tupleSize": 4, + "numberOfTuples": 2, + "zeroPadding": "left", + "reverseObjectRoot": true +} +``` + +#### Mappings + +| Object ID | Object Root Path | +| --- | --- | +| namespace:12887296 | `6927/8821/12887296` | +| urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66 | `66a9/c002/6e8bc430-9c3a-11d9-9669-0800200c9a66` | +| abc123 | `321c/ba00/abc123` + +#### Storage Hierarchy + +``` +[storage_root]/ +├── 0=ocfl_1.0 +├── ocfl_layout.json +├── extensions/ +│ └── 0007-n-tuple-omit-prefix-storage-layout/ +│ └── config.json +├── 6927/ +│ └── 8821/ +│ └── 12887296/ +│ ├── 0=ocfl_object_1.0 +│ ├── inventory.json +│ ├── inventory.json.sha512 +│ └── v1 [...] +├── 66a9/ +│ └── c002/ +│ └── 6e8bc430-9c3a-11d9-9669-0800200c9a66/ +│ ├── 0=ocfl_object_1.0 +│ ├── inventory.json +│ ├── inventory.json.sha512 +│ └── v1 [...] +└── 321c/ + └── ba00/ + └── abc123/ + ├── 0=ocfl_object_1.0 + ├── inventory.json + ├── inventory.json.sha512 + └── v1 [...] + +``` + +### Example 2 + +This example demonstrates mappings where the multi-character delimiter is found one or more times in the OCFL object +identifier, with default `tupleSize`, `numberOfTuples`, and `reverseObjectRoot`, and modified `zeroPadding`. + +#### Parameters + +```json +{ + "extensionName": "0007-n-tuple-omit-prefix-storage-layout", + "delimiter": "edu/", + "tupleSize": 3, + "numberOfTuples": 3, + "zeroPadding": "right", + "reverseObjectRoot": false +} +``` + +#### Mappings + +| Object ID | Object Root Path | +| --- | --- | +| https://institution.edu/3448793 | `344/879/300/3448793` | +| https://institution.edu/abc/edu/f8.05v | `f8./05v/000/f8.05v` | + +#### Storage Hierarchy + +``` +[storage_root]/ +├── 0=ocfl_1.0 +├── ocfl_layout.json +├── extensions/ +│ └── 0007-n-tuple-omit-prefix-storage-layout/ +│ └── config.json +├── 344/ +│ └── 879/ +│ └── 300/ +│ └── 3448793/ +│ ├── 0=ocfl_object_1.0 +│ ├── inventory.json +│ ├── inventory.json.sha512 +│ └── v1 [...] +└── f8./ + └── 05v/ + └── 000/ + └── f8.05v/ + ├── 0=ocfl_object_1.0 + ├── inventory.json + ├── inventory.json.sha512 + └── v1 [...] +``` + diff --git a/ocfl-java-itest/pom.xml b/ocfl-java-itest/pom.xml index 01d6ac4c..96ecc267 100644 --- a/ocfl-java-itest/pom.xml +++ b/ocfl-java-itest/pom.xml @@ -31,12 +31,12 @@ edu.wisc.library.ocfl ocfl-java-parent - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT ../pom.xml ocfl-java-itest - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT OCFL Java Integration Tests Integration tests for the OCFL Java library. @@ -80,19 +80,19 @@ edu.wisc.library.ocfl ocfl-java-core - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT edu.wisc.library.ocfl ocfl-java-aws - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT edu.wisc.library.ocfl ocfl-java-test - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT test diff --git a/ocfl-java-test/pom.xml b/ocfl-java-test/pom.xml index b54ff257..8c0d1ec9 100644 --- a/ocfl-java-test/pom.xml +++ b/ocfl-java-test/pom.xml @@ -31,12 +31,12 @@ edu.wisc.library.ocfl ocfl-java-parent - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT ../pom.xml ocfl-java-test - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT OCFL Java Test Helpers Common utilities used for constructing tests @@ -71,7 +71,7 @@ edu.wisc.library.ocfl ocfl-java-api - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT org.junit.jupiter diff --git a/pom.xml b/pom.xml index 5126c9ec..28547571 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ edu.wisc.library.ocfl ocfl-java-parent - 1.4.3-SNAPSHOT + 1.4.2-SNAPSHOT pom OCFL Java Parent POM From 5277d31d1f46de7b656cc72d87f3f26fa78359d8 Mon Sep 17 00:00:00 2001 From: vac765 Date: Tue, 25 Jan 2022 11:06:04 -0500 Subject: [PATCH 8/8] Back to 1.4.3-SNAPSHOT --- ocfl-java-api/pom.xml | 4 ++-- ocfl-java-aws/pom.xml | 8 ++++---- ocfl-java-core/pom.xml | 8 ++++---- ocfl-java-itest/pom.xml | 10 +++++----- ocfl-java-test/pom.xml | 6 +++--- pom.xml | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ocfl-java-api/pom.xml b/ocfl-java-api/pom.xml index 58edc825..2851142c 100644 --- a/ocfl-java-api/pom.xml +++ b/ocfl-java-api/pom.xml @@ -31,12 +31,12 @@ edu.wisc.library.ocfl ocfl-java-parent - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT ../pom.xml ocfl-java-api - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT OCFL Java API Java API for interacting with objects stored in OCFL. diff --git a/ocfl-java-aws/pom.xml b/ocfl-java-aws/pom.xml index 9a73fd56..27dcc8af 100644 --- a/ocfl-java-aws/pom.xml +++ b/ocfl-java-aws/pom.xml @@ -31,12 +31,12 @@ edu.wisc.library.ocfl ocfl-java-parent - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT ../pom.xml ocfl-java-aws - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT OCFL Java AWS Java OCFL implementation that writes to S3. @@ -81,7 +81,7 @@ edu.wisc.library.ocfl ocfl-java-core - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT @@ -112,7 +112,7 @@ edu.wisc.library.ocfl ocfl-java-test - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT test diff --git a/ocfl-java-core/pom.xml b/ocfl-java-core/pom.xml index 0fe31f95..766fd24a 100644 --- a/ocfl-java-core/pom.xml +++ b/ocfl-java-core/pom.xml @@ -31,12 +31,12 @@ edu.wisc.library.ocfl ocfl-java-parent - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT ../pom.xml ocfl-java-core - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT OCFL Java Core Core Java OCFL implementation code. @@ -74,7 +74,7 @@ edu.wisc.library.ocfl ocfl-java-api - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT @@ -127,7 +127,7 @@ edu.wisc.library.ocfl ocfl-java-test - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT test diff --git a/ocfl-java-itest/pom.xml b/ocfl-java-itest/pom.xml index 96ecc267..01d6ac4c 100644 --- a/ocfl-java-itest/pom.xml +++ b/ocfl-java-itest/pom.xml @@ -31,12 +31,12 @@ edu.wisc.library.ocfl ocfl-java-parent - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT ../pom.xml ocfl-java-itest - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT OCFL Java Integration Tests Integration tests for the OCFL Java library. @@ -80,19 +80,19 @@ edu.wisc.library.ocfl ocfl-java-core - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT edu.wisc.library.ocfl ocfl-java-aws - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT edu.wisc.library.ocfl ocfl-java-test - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT test diff --git a/ocfl-java-test/pom.xml b/ocfl-java-test/pom.xml index 8c0d1ec9..b54ff257 100644 --- a/ocfl-java-test/pom.xml +++ b/ocfl-java-test/pom.xml @@ -31,12 +31,12 @@ edu.wisc.library.ocfl ocfl-java-parent - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT ../pom.xml ocfl-java-test - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT OCFL Java Test Helpers Common utilities used for constructing tests @@ -71,7 +71,7 @@ edu.wisc.library.ocfl ocfl-java-api - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT org.junit.jupiter diff --git a/pom.xml b/pom.xml index 28547571..5126c9ec 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ edu.wisc.library.ocfl ocfl-java-parent - 1.4.2-SNAPSHOT + 1.4.3-SNAPSHOT pom OCFL Java Parent POM