[java-opensaml] branch main updated: OSJ-304: Support hash-based directory structure in LocalDynamic
Brent Putman
putmanb at georgetown.edu
Fri Nov 6 02:41:07 UTC 2020
This is an automated email from the git hooks/post-receive script.
putmanb pushed a commit to branch main
in repository java-opensaml.
View the commit online:
http://git.shibboleth.net/view/?p=java-opensaml.git;a=commit;h=5a5ecb18728517ea13f20572b2785c9690a8e4d8
The following commit(s) were added to refs/heads/main by this push:
new 5a5ecb187 OSJ-304: Support hash-based directory structure in LocalDynamic
5a5ecb187 is described below
commit 5a5ecb18728517ea13f20572b2785c9690a8e4d8
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Thu Aug 6 22:47:17 2020 -0400
OSJ-304: Support hash-based directory structure in LocalDynamic
---
.../xml/persist/FilesystemLoadSaveManager.java | 158 +++++++++++++++------
.../persist/impl/PassthroughSourceStrategy.java | 37 +++++
.../SegmentingIntermediateDirectoryStrategy.java | 99 +++++++++++++
.../xml/persist/FilesystemLoadSaveManagerTest.java | 113 ++++++++++++++-
...egmentingIntermediateDirectoryStrategyTest.java | 55 +++++++
5 files changed, 412 insertions(+), 50 deletions(-)
diff --git a/opensaml-core/src/main/java/org/opensaml/core/xml/persist/FilesystemLoadSaveManager.java b/opensaml-core/src/main/java/org/opensaml/core/xml/persist/FilesystemLoadSaveManager.java
index 4c7f9a2c5..21ce3936f 100644
--- a/opensaml-core/src/main/java/org/opensaml/core/xml/persist/FilesystemLoadSaveManager.java
+++ b/opensaml-core/src/main/java/org/opensaml/core/xml/persist/FilesystemLoadSaveManager.java
@@ -19,18 +19,17 @@ package org.opensaml.core.xml.persist;
import java.io.ByteArrayInputStream;
import java.io.File;
-import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.file.Path;
import java.time.Instant;
import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
+import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
@@ -78,12 +77,13 @@ public class FilesystemLoadSaveManager<T extends XMLObject> extends AbstractCond
/** The base directory used for storing individual serialized XML files. */
private File baseDirectory;
+ /** Optional strategy function which produces the intermediate directory path(s) between
+ * the <code>baseDirectory</code> and the actual file. */
+ private Function<String, List<String>> intermediateDirectoryStrategy;
+
/** Parser pool instance for deserializing XML from the filesystem. */
private ParserPool parserPool;
- /** File filter used in filtering files in {@link #listKeys()} and {@link #listAll()}. */
- private FileFilter fileFilter;
-
/**
* Constructor.
*
@@ -94,23 +94,25 @@ public class FilesystemLoadSaveManager<T extends XMLObject> extends AbstractCond
this(new File(Constraint.isNotNull(StringSupport.trimOrNull(baseDir),
"Base directory string instance was null or empty")),
null,
- false);
+ false,
+ null);
}
/**
* Constructor.
*
* @param baseDir the base directory, must be an absolute path
- * @param conditionalLoad whether {@link #load(String)} should behave
+ * @param conditionalLoad whether {@link #load(String)} should behave
* as defined in {@link ConditionalLoadXMLObjectLoadSaveManager}
*/
public FilesystemLoadSaveManager(
- @ParameterName(name="baseDir") @Nonnull final String baseDir,
+ @ParameterName(name="baseDir") @Nonnull final String baseDir,
@ParameterName(name="conditionalLoad") final boolean conditionalLoad) {
this(new File(Constraint.isNotNull(StringSupport.trimOrNull(baseDir),
"Base directory string instance was null or empty")),
null,
- conditionalLoad);
+ conditionalLoad,
+ null);
}
/**
@@ -120,9 +122,22 @@ public class FilesystemLoadSaveManager<T extends XMLObject> extends AbstractCond
*/
public FilesystemLoadSaveManager(
@ParameterName(name="baseDirFile") @Nonnull final File baseDir) {
- this(baseDir, null, false);
+ this(baseDir, null, false, null);
}
+ /**
+ * Constructor.
+ *
+ * @param baseDir the base directory, must be an absolute path
+ * @param dirStrategy the intermediate directory strategy
+ */
+ public FilesystemLoadSaveManager(
+ @ParameterName(name="baseDirFile") @Nonnull final File baseDir,
+ @ParameterName(name="intermediateDirectoryStrategy")
+ @Nullable final Function<String, List<String>> dirStrategy) {
+ this(baseDir, null, false, dirStrategy);
+ }
+
/**
* Constructor.
*
@@ -133,9 +148,25 @@ public class FilesystemLoadSaveManager<T extends XMLObject> extends AbstractCond
public FilesystemLoadSaveManager(
@ParameterName(name="baseDirFile") @Nonnull final File baseDir,
@ParameterName(name="conditionalLoad") final boolean conditionalLoad) {
- this(baseDir, null, conditionalLoad);
+ this(baseDir, null, conditionalLoad, null);
}
+ /**
+ * Constructor.
+ *
+ * @param baseDir the base directory, must be an absolute path
+ * @param conditionalLoad whether {@link #load(String)} should behave
+ * as defined in {@link ConditionalLoadXMLObjectLoadSaveManager}
+ * @param dirStrategy the intermediate directory strategy
+ */
+ public FilesystemLoadSaveManager(
+ @ParameterName(name="baseDirFile") @Nonnull final File baseDir,
+ @ParameterName(name="conditionalLoad") final boolean conditionalLoad,
+ @ParameterName(name="intermediateDirectoryStrategy")
+ @Nullable final Function<String, List<String>> dirStrategy) {
+ this(baseDir, null, conditionalLoad, dirStrategy);
+ }
+
/**
* Constructor.
*
@@ -148,7 +179,8 @@ public class FilesystemLoadSaveManager<T extends XMLObject> extends AbstractCond
this(new File(Constraint.isNotNull(StringSupport.trimOrNull(baseDir),
"Base directory string instance was null or empty")),
pp,
- false);
+ false,
+ null);
}
/**
@@ -165,7 +197,7 @@ public class FilesystemLoadSaveManager<T extends XMLObject> extends AbstractCond
@ParameterName(name="conditionalLoad") final boolean conditionalLoad) {
this(new File(Constraint.isNotNull(StringSupport.trimOrNull(baseDir),
"Base directory string instance was null or empty")),
- pp, conditionalLoad);
+ pp, conditionalLoad, null);
}
/**
* Constructor.
@@ -176,22 +208,40 @@ public class FilesystemLoadSaveManager<T extends XMLObject> extends AbstractCond
public FilesystemLoadSaveManager(
@ParameterName(name="baseDirFile") @Nonnull final File baseDir,
@ParameterName(name="parserPool") @Nullable final ParserPool pp) {
- this(baseDir, pp, false);
+ this(baseDir, pp, false, null);
}
-
+
/**
* Constructor.
*
* @param baseDir the base directory, must be an absolute path
* @param pp the parser pool instance to use
- * @param conditionalLoad whether {@link #load(String)} should behave
+ * @param conditionalLoad whether {@link #load(String)} should behave
* as defined in {@link ConditionalLoadXMLObjectLoadSaveManager}
*/
public FilesystemLoadSaveManager(
- @ParameterName(name="baseDirFile") @Nonnull final File baseDir,
+ @ParameterName(name="baseDirFile") @Nonnull final File baseDir,
@ParameterName(name="parserPool") @Nullable final ParserPool pp,
@ParameterName(name="conditionalLoad") final boolean conditionalLoad) {
-
+ this(baseDir, pp, conditionalLoad, null);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param baseDir the base directory, must be an absolute path
+ * @param pp the parser pool instance to use
+ * @param conditionalLoad whether {@link #load(String)} should behave
+ * as defined in {@link ConditionalLoadXMLObjectLoadSaveManager}
+ * @param dirStrategy the intermediate directory strategy
+ */
+ public FilesystemLoadSaveManager(
+ @ParameterName(name="baseDirFile") @Nonnull final File baseDir,
+ @ParameterName(name="parserPool") @Nullable final ParserPool pp,
+ @ParameterName(name="conditionalLoad") final boolean conditionalLoad,
+ @ParameterName(name="intermediateDirectoryStrategy")
+ @Nullable final Function<String, List<String>> dirStrategy) {
+
super(conditionalLoad);
baseDirectory = Constraint.isNotNull(baseDir, "Base directory File instance was null");
@@ -207,18 +257,17 @@ public class FilesystemLoadSaveManager<T extends XMLObject> extends AbstractCond
parserPool = Constraint.isNotNull(XMLObjectProviderRegistrySupport.getParserPool(),
"Specified ParserPool was null and global ParserPool was not available");
}
-
- fileFilter = new DefaultFileFilter();
+
+ intermediateDirectoryStrategy = dirStrategy;
}
/** {@inheritDoc} */
public Set<String> listKeys() throws IOException {
- final File[] files = baseDirectory.listFiles(fileFilter);
- final HashSet<String> keys = new HashSet<>();
- for (final File file : files) {
- keys.add(file.getName());
- }
- return Collections.unmodifiableSet(keys);
+ return java.nio.file.Files.walk(baseDirectory.toPath())
+ .filter(java.nio.file.Files::isRegularFile)
+ .map(Path::getFileName)
+ .map(Path::toString)
+ .collect(Collectors.toUnmodifiableSet());
}
/** {@inheritDoc} */
@@ -280,6 +329,9 @@ public class FilesystemLoadSaveManager<T extends XMLObject> extends AbstractCond
}
final File file = buildFile(key);
+
+ checkAndCreateIntermediateDirectories(file);
+
try (FileOutputStream fos = new FileOutputStream(file)) {
final List<XMLObjectSource> sources = xmlObject.getObjectMetadata().get(XMLObjectSource.class);
if (sources.size() == 1) {
@@ -325,12 +377,34 @@ public class FilesystemLoadSaveManager<T extends XMLObject> extends AbstractCond
if (newFile.exists()) {
throw new IOException(String.format("Specified new key already exists: %s", newKey));
}
+
+ checkAndCreateIntermediateDirectories(newFile);
+
Files.move(currentFile, newFile);
updateLoadLastModified(newKey, getLoadLastModified(currentKey));
clearLoadLastModified(currentKey);
return true;
}
-
+
+ /**
+ * Check and create intermediate directories between the <code>baseDirectory</code> and the actual file,
+ * if necessary.
+ *
+ * @param file the target file whose path is to be evaluated
+ *
+ * @throws IOException if the intermediate directory creation fails
+ */
+ protected void checkAndCreateIntermediateDirectories(@Nonnull final File file) throws IOException {
+ final File parentDir = new File(file.getParent());
+
+ if (!baseDirectory.equals(parentDir) && !parentDir.exists()) {
+ if (!parentDir.mkdirs()) {
+ throw new IOException(String.format("Could not create intermediate directories for target file: %s",
+ file.getAbsolutePath()));
+ }
+ }
+ }
+
/**
* Build the target file name from the specified index key and the configured base directory.
*
@@ -339,7 +413,17 @@ public class FilesystemLoadSaveManager<T extends XMLObject> extends AbstractCond
* @throws IOException if there is a fatal error constructing or evaluating the candidate target path
*/
protected File buildFile(final String key) throws IOException {
- final File path = new File(baseDirectory,
+ File parentDirectory = baseDirectory;
+ if (intermediateDirectoryStrategy != null) {
+ final List<String> intermediateDirs = intermediateDirectoryStrategy.apply(key);
+ if (intermediateDirs != null && !intermediateDirs.isEmpty()) {
+ for (final String dir : intermediateDirs) {
+ parentDirectory = new File(parentDirectory, dir);
+ }
+ }
+ }
+
+ final File path = new File(parentDirectory,
Constraint.isNotNull(StringSupport.trimOrNull(key), "Input key was null or empty"));
if (path.exists() && !path.isFile()) {
throw new IOException(String.format("Path exists based on specified key, but is not a file: %s",
@@ -348,22 +432,6 @@ public class FilesystemLoadSaveManager<T extends XMLObject> extends AbstractCond
return path;
}
- /**
- * Default filter used to filter data returned in {@link FilesystemLoadSaveManager#listKeys()}
- * and {@link FilesystemLoadSaveManager#listAll()}.
- */
- public static class DefaultFileFilter implements FileFilter {
-
- /** {@inheritDoc} */
- public boolean accept(final File pathname) {
- if (pathname == null) {
- return false;
- }
- return pathname.isFile();
- }
-
- }
-
/**
* Iterable which provides lazy iteration over the managed files.
*/
diff --git a/opensaml-core/src/main/java/org/opensaml/core/xml/persist/impl/PassthroughSourceStrategy.java b/opensaml-core/src/main/java/org/opensaml/core/xml/persist/impl/PassthroughSourceStrategy.java
new file mode 100644
index 000000000..ab7cb5aa1
--- /dev/null
+++ b/opensaml-core/src/main/java/org/opensaml/core/xml/persist/impl/PassthroughSourceStrategy.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.opensaml.core.xml.persist.impl;
+
+import java.util.function.Function;
+
+/**
+ * Pass-through source strategy function.
+ *
+ * <p>
+ * Typically used with {@link SegmentingIntermediateDirectoryStrategy}.
+ * </p>
+ */
+public class PassthroughSourceStrategy implements Function<String, String> {
+
+ /** {@inheritDoc} */
+ public String apply(final String key) {
+ return key;
+ }
+
+
+}
diff --git a/opensaml-core/src/main/java/org/opensaml/core/xml/persist/impl/SegmentingIntermediateDirectoryStrategy.java b/opensaml-core/src/main/java/org/opensaml/core/xml/persist/impl/SegmentingIntermediateDirectoryStrategy.java
new file mode 100644
index 000000000..062976c18
--- /dev/null
+++ b/opensaml-core/src/main/java/org/opensaml/core/xml/persist/impl/SegmentingIntermediateDirectoryStrategy.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.opensaml.core.xml.persist.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.core.xml.XMLRuntimeException;
+import org.opensaml.core.xml.persist.FilesystemLoadSaveManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.utilities.java.support.annotation.ParameterName;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Strategy function for producing intermediate directories from an input key.
+ *
+ * <p>
+ * Typically used with {@link FilesystemLoadSaveManager}.
+ * </p>
+ */
+public class SegmentingIntermediateDirectoryStrategy implements Function<String, List<String>> {
+
+ /** Logger. **/
+ private Logger log = LoggerFactory.getLogger(SegmentingIntermediateDirectoryStrategy.class);
+
+ /** Strategy function for generating the source data from the input key.*/
+ private Function<String,String> sourceStrategy;
+
+ /** The number of segments to produce. **/
+ private int segmentNumber;
+
+ /** The length of each produced segment. **/
+ private int segmentLength;
+
+ /**
+ * Constructor.
+ * @param number number of segments
+ * @param length length of each segment
+ * @param source source strategy function
+ */
+ public SegmentingIntermediateDirectoryStrategy(
+ @ParameterName(name="segmentNumber") final int number,
+ @ParameterName(name="segmentLength") final int length,
+ @ParameterName(name="sourceStrategy") final @Nonnull Function<String,String> source) {
+ segmentNumber = Constraint.isGreaterThan(0, number, "Number of segments was zero");
+ segmentLength = Constraint.isGreaterThan(0, length, "Length of segments was zero");
+ sourceStrategy = Constraint.isNotNull(source, "Source strategy was null");
+ }
+
+ /** {@inheritDoc} */
+ public List<String> apply(final String key) {
+ final String source = sourceStrategy.apply(key);
+ if (source == null || source.length() == 0) {
+ log.trace("Source strategy returned null or empty, returning null");
+ return null;
+ }
+
+ log.trace("Resolved source: {}", source);
+
+ if (source.length() < segmentNumber * segmentLength) {
+ final String msg = String.format("Source length %d is less than number (%d) * length (%d) of segments: %s",
+ source.length(), segmentNumber, segmentLength, source);
+ log.warn(msg);
+ throw new XMLRuntimeException(msg);
+ }
+
+ final ArrayList<String> segments = new ArrayList<>();
+ for (int i=0; i<segmentNumber; i++) {
+ final int startIndex = i * segmentLength;
+ final int endIndex = startIndex + segmentLength;
+ final String segment = key.substring(startIndex, endIndex);
+ log.trace("Produced directory segment: {}", segment);
+ segments.add(segment);
+ }
+
+ return segments;
+ }
+
+}
diff --git a/opensaml-core/src/test/java/org/opensaml/core/xml/persist/FilesystemLoadSaveManagerTest.java b/opensaml-core/src/test/java/org/opensaml/core/xml/persist/FilesystemLoadSaveManagerTest.java
index a1777e909..6297d49d1 100644
--- a/opensaml-core/src/test/java/org/opensaml/core/xml/persist/FilesystemLoadSaveManagerTest.java
+++ b/opensaml-core/src/test/java/org/opensaml/core/xml/persist/FilesystemLoadSaveManagerTest.java
@@ -21,12 +21,16 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.time.Instant;
import java.util.Collections;
+import java.util.Comparator;
import java.util.Iterator;
+import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
import javax.xml.namespace.QName;
@@ -35,6 +39,8 @@ import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.XMLRuntimeException;
import org.opensaml.core.xml.io.MarshallingException;
import org.opensaml.core.xml.mock.SimpleXMLObject;
+import org.opensaml.core.xml.persist.impl.PassthroughSourceStrategy;
+import org.opensaml.core.xml.persist.impl.SegmentingIntermediateDirectoryStrategy;
import org.opensaml.core.xml.util.XMLObjectSource;
import org.opensaml.core.xml.util.XMLObjectSupport;
import org.slf4j.Logger;
@@ -58,15 +64,21 @@ public class FilesystemLoadSaveManagerTest extends XMLObjectBaseTestCase {
private FilesystemLoadSaveManager<SimpleXMLObject> manager;
+ private Function<String, List<String>> intermediateDirectoryStrategy;
+
@BeforeMethod
public void setUp() throws IOException {
baseDir = new File(System.getProperty("java.io.tmpdir"), "load-save-manager-test");
baseDir.deleteOnExit();
log.debug("Using base directory: {}", baseDir.getAbsolutePath());
resetBaseDir();
- Assert.assertTrue(baseDir.mkdirs());
+ if (!baseDir.exists()) {
+ Assert.assertTrue(baseDir.mkdirs());
+ }
manager = new FilesystemLoadSaveManager<>(baseDir);
+
+ intermediateDirectoryStrategy = new SegmentingIntermediateDirectoryStrategy(1, 2, new PassthroughSourceStrategy());
}
@AfterMethod
@@ -144,6 +156,80 @@ public class FilesystemLoadSaveManagerTest extends XMLObjectBaseTestCase {
testState(Collections.emptySet());
}
+ @Test(dataProvider="saveLoadUpdateRemoveParams")
+ public void saveLoadUpdateRemoveWithIntermediateDirs(Boolean buildWithObjectSourceByteArray) throws IOException {
+ manager = new FilesystemLoadSaveManager<>(baseDir, intermediateDirectoryStrategy);
+
+ testState(Collections.emptySet());
+
+ Assert.assertNull(manager.load("bogus"));
+
+ Assert.assertFalse(new File(parentPath(baseDir, "fo"), "foo").exists());
+ manager.save("foo", (SimpleXMLObject) buildXMLObject(SimpleXMLObject.ELEMENT_NAME, buildWithObjectSourceByteArray));
+ testState(Collections.singleton("foo"));
+ Assert.assertTrue(new File(parentPath(baseDir, "fo"), "foo").exists());
+
+ Assert.assertFalse(new File(parentPath(baseDir, "ba"), "bar").exists());
+ Assert.assertFalse(new File(parentPath(baseDir, "ba"), "baz").exists());
+ manager.save("bar", (SimpleXMLObject) buildXMLObject(SimpleXMLObject.ELEMENT_NAME, buildWithObjectSourceByteArray));
+ manager.save("baz", (SimpleXMLObject) buildXMLObject(SimpleXMLObject.ELEMENT_NAME, buildWithObjectSourceByteArray));
+ testState(Set.of("foo", "bar", "baz"));
+ Assert.assertTrue(new File(parentPath(baseDir, "ba"), "bar").exists());
+ Assert.assertTrue(new File(parentPath(baseDir, "ba"), "baz").exists());
+
+ // Duplicate with overwrite
+ manager.save("bar", (SimpleXMLObject) buildXMLObject(SimpleXMLObject.ELEMENT_NAME, buildWithObjectSourceByteArray), true);
+ testState(Set.of("foo", "bar", "baz"));
+
+ // Duplicate without overwrite
+ try {
+ manager.save("bar", (SimpleXMLObject) buildXMLObject(SimpleXMLObject.ELEMENT_NAME, buildWithObjectSourceByteArray), false);
+ Assert.fail("Should have failed on duplicate save without overwrite");
+ } catch (IOException e) {
+ // expected, do nothing
+ }
+ testState(Set.of("foo", "bar", "baz"));
+
+ // Test again. Since checkModifyTime=false, we should get back data even though unmodified
+ testState(Set.of("foo", "bar", "baz"));
+
+ Assert.assertFalse(new File(parentPath(baseDir, "fo"), "foo2").exists());
+ Assert.assertTrue(manager.updateKey("foo", "foo2"));
+ testState(Set.of("foo2", "bar", "baz"));
+ Assert.assertTrue(new File(parentPath(baseDir, "fo"), "foo2").exists());
+
+ // Doesn't exist anymore
+ Assert.assertFalse(manager.updateKey("foo", "foo2"));
+ testState(Set.of("foo2", "bar", "baz"));
+
+ // Can't update to an existing name
+ try {
+ manager.updateKey("bar", "baz");
+ Assert.fail("updateKey should have failed to due existing new key name");
+ } catch (IOException e) {
+ // expected, do nothing
+ }
+ testState(Set.of("foo2", "bar", "baz"));
+
+ // Doesn't exist anymore
+ Assert.assertFalse(manager.remove("foo"));
+ testState(Set.of("foo2", "bar", "baz"));
+ Assert.assertFalse(new File(parentPath(baseDir, "fo"), "foo").exists());
+
+ Assert.assertTrue(new File(parentPath(baseDir, "fo"), "foo2").exists());
+ Assert.assertTrue(manager.remove("foo2"));
+ testState(Set.of("bar", "baz"));
+ Assert.assertFalse(new File(parentPath(baseDir, "fo"), "foo2").exists());
+
+ Assert.assertTrue(new File(parentPath(baseDir, "ba"), "bar").exists());
+ Assert.assertTrue(new File(parentPath(baseDir, "ba"), "baz").exists());
+ Assert.assertTrue(manager.remove("bar"));
+ Assert.assertTrue(manager.remove("baz"));
+ testState(Collections.emptySet());
+ Assert.assertFalse(new File(parentPath(baseDir, "ba"), "bar").exists());
+ Assert.assertFalse(new File(parentPath(baseDir, "ba"), "baz").exists());
+ }
+
@Test
public void checkCheckModifyTimeTracking() throws IOException {
manager = new FilesystemLoadSaveManager<>(baseDir, true);
@@ -196,6 +282,13 @@ public class FilesystemLoadSaveManagerTest extends XMLObjectBaseTestCase {
Assert.assertEquals(target, new File(baseDir, "abc"));
}
+ @Test
+ public void buildTargetFileFromKeyWithIntermediateDirs() throws IOException {
+ manager = new FilesystemLoadSaveManager<>(baseDir, intermediateDirectoryStrategy);
+ File target = manager.buildFile("abc");
+ Assert.assertEquals(target, new File(parentPath(baseDir, "ab"), "abc"));
+ }
+
@Test(expectedExceptions=IOException.class)
public void targetExistsButIsNotAFile() throws IOException {
File target = new File(baseDir, "abc");
@@ -341,6 +434,14 @@ public class FilesystemLoadSaveManagerTest extends XMLObjectBaseTestCase {
// Helpers
+ private File parentPath(File base, String ... dirs) {
+ File parentPath = base;
+ for (String dir : dirs) {
+ parentPath = new File(parentPath, dir);
+ }
+ return parentPath;
+ }
+
private void testState(Set<String> expectedKeys) throws IOException {
Assert.assertEquals(manager.listKeys().isEmpty(), expectedKeys.isEmpty() ? true : false);
Assert.assertEquals(manager.listKeys(), expectedKeys);
@@ -365,11 +466,13 @@ public class FilesystemLoadSaveManagerTest extends XMLObjectBaseTestCase {
private void resetBaseDir() throws IOException {
if (baseDir.exists()) {
if (baseDir.isDirectory()) {
- for (File child : baseDir.listFiles()) {
- Files.delete(child.toPath());
- }
+ Files.walk(baseDir.toPath())
+ .sorted(Comparator.reverseOrder())
+ .map(Path::toFile)
+ .forEach(File::delete);
+ } else {
+ baseDir.delete();
}
- Files.delete(baseDir.toPath());
}
}
diff --git a/opensaml-core/src/test/java/org/opensaml/core/xml/persist/impl/SegmentingIntermediateDirectoryStrategyTest.java b/opensaml-core/src/test/java/org/opensaml/core/xml/persist/impl/SegmentingIntermediateDirectoryStrategyTest.java
new file mode 100644
index 000000000..1f66eef89
--- /dev/null
+++ b/opensaml-core/src/test/java/org/opensaml/core/xml/persist/impl/SegmentingIntermediateDirectoryStrategyTest.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.opensaml.core.xml.persist.impl;
+
+import java.util.function.Function;
+
+import org.opensaml.core.xml.XMLRuntimeException;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.Lists;
+
+/**
+ *
+ */
+public class SegmentingIntermediateDirectoryStrategyTest {
+
+ @Test
+ public void basic() {
+ SegmentingIntermediateDirectoryStrategy strategy = null;
+ Function<String, String> sourceStrategy = new PassthroughSourceStrategy();
+
+ strategy = new SegmentingIntermediateDirectoryStrategy(1, 2, sourceStrategy);
+ Assert.assertEquals(strategy.apply("aabbccddeeffgg"), Lists.newArrayList("aa"));
+
+ strategy = new SegmentingIntermediateDirectoryStrategy(3, 2, sourceStrategy);
+ Assert.assertEquals(strategy.apply("aabbccddeeffgg"), Lists.newArrayList("aa", "bb", "cc"));
+
+ strategy = new SegmentingIntermediateDirectoryStrategy(3, 4, sourceStrategy);
+ Assert.assertEquals(strategy.apply("aabbccddeeffgg"), Lists.newArrayList("aabb", "ccdd", "eeff"));
+
+ }
+
+ @Test(expectedExceptions = XMLRuntimeException.class)
+ public void sourceTooShort() {
+ SegmentingIntermediateDirectoryStrategy strategy = new SegmentingIntermediateDirectoryStrategy(8, 2, new PassthroughSourceStrategy());
+ strategy.apply("aabbccddeeffgg");
+ }
+
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list