Skip to content

Commit

Permalink
Add public API method 'deleteMetadata()' plus new junit tests and upd…
Browse files Browse the repository at this point in the history
…ate javadocs
  • Loading branch information
doulikecookiedough committed Jun 29, 2023
1 parent 5c49d04 commit 9ac1933
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 17 deletions.
6 changes: 5 additions & 1 deletion src/main/java/org/dataone/hashstore/HashStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,11 @@ InputStream retrieveObject(String pid)
* @param pid Authority-based identifier
* @param formatId Metadata namespace/format
* @return
* @throws Exception TODO: Add specific exceptions
* @throws IllegalArgumentException When pid or formatId is null or empty
* @throws FileNotFoundException When requested pid has no metadata
* @throws IOException I/O error when deleting empty directories
* @throws NoSuchAlgorithmException When algorithm used to calcualte object
* address is not supported
*/
boolean deleteMetadata(String pid, String formatId) throws Exception;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
Expand All @@ -24,6 +23,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.stream.Stream;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
Expand Down Expand Up @@ -657,9 +657,56 @@ public boolean deleteObject(String pid)
}

@Override
public boolean deleteMetadata(String pid, String formatId) throws Exception {
// TODO: Implement method
return false;
public boolean deleteMetadata(String pid, String formatId)
throws IllegalArgumentException, FileNotFoundException, IOException, NoSuchAlgorithmException {
logFileHashStore.debug("FileHashStore.deleteMetadata - Called to delete metadata for pid: " + pid);

if (pid == null || pid.trim().isEmpty()) {
String errMsg = "FileHashStore.deleteMetadata - pid cannot be null or empty, pid: " + pid;
logFileHashStore.error(errMsg);
throw new IllegalArgumentException(errMsg);
}
if (formatId == null || formatId.trim().isEmpty()) {
String errMsg = "FileHashStore.deleteMetadata - formatId cannot be null or empty, formatId: " + pid;
logFileHashStore.error(errMsg);
throw new IllegalArgumentException(errMsg);
}

// Get permanent address of the pid by calculating its sha-256 hex digest
String metadataCid = this.getPidHexDigest(pid + formatId, OBJECT_STORE_ALGORITHM);
String metadataCidShardString = this.getHierarchicalPathString(this.DIRECTORY_DEPTH, this.DIRECTORY_WIDTH,
metadataCid);
Path metadataCidPath = this.METADATA_STORE_DIRECTORY.resolve(metadataCidShardString);

// Check to see if object exists
if (!Files.exists(metadataCidPath)) {
String errMsg = "FileHashStore.deleteMetadata - File does not exist for pid: " + pid
+ " with metadata address: " + metadataCidPath;
logFileHashStore.warn(errMsg);
throw new FileNotFoundException(errMsg);
}

// Delete file
Files.delete(metadataCidPath);

// Then delete any empty directories
Path parent = metadataCidPath.getParent();
while (parent != null && isDirectoryEmpty(parent)) {
if (parent.equals(this.METADATA_STORE_DIRECTORY)) {
// Do not delete the metadata store directory
break;

} else {
Files.delete(parent);
logFileHashStore.info("FileHashStore.deleteMetadata - Deleting parent directory for: " + pid
+ " with parent address: " + parent);
parent = parent.getParent();
}
}

logFileHashStore.info("FileHashStore.deleteMetadata - File deleted for: " + pid + " with metadata address: "
+ metadataCidPath);
return true;
}

@Override
Expand Down Expand Up @@ -1295,16 +1342,24 @@ protected boolean writeToTmpMetadataFile(File tmpFile, InputStream metadataStrea
}

/**
* Determines whether a given directory is empty or not
*
* Checks whether a directory is empty or contains files. If a file is found, it
* returns true.
*
* @param directory Directory to check
* @return False if not empty
* @throws IOException If I/O occurs when calling Files for a new directory
* stream
* @return True if a file is found or the directory is empty, False otherwise
* @throws IOException If I/O occurs when accessing directory
*/
private static boolean isDirectoryEmpty(Path directory) throws IOException {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
return !stream.iterator().hasNext();
try (Stream<Path> stream = Files.list(directory)) {
// The findFirst() method is called on the stream created from the given
// directory to retrieve the first element. If the stream is empty (i.e., the
// directory is empty), findFirst() will return an empty Optional<Path>.
//
// The isPresent() method is called on the Optional<Path> returned by
// findFirst(). If the Optional contains a value (i.e., an element was found),
// isPresent() returns true. If the Optional is empty (i.e., the stream is
// empty), isPresent() returns false.
return !stream.findFirst().isPresent();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -944,42 +944,140 @@ public void deleteObject() throws Exception {
// Double check that file doesn't exist
assertFalse(Files.exists(objInfo.getAbsPath()));

// Double check that store root still exists
// Double check that object directory still exists
Path storePath = (Path) this.fhsProperties.get("storePath");
Path storeObjectPath = storePath.resolve("objects");
assertTrue(Files.exists(storeObjectPath));
}
}

/**
* Confirm that deleteObject deletes object and empty sub directories
* Confirm that deleteObject throws exception when associated pid obj not found
*/
@Test(expected = FileNotFoundException.class)
public void deleteObject_pidNotFound() throws Exception {
fileHashStore.deleteObject("dou.2023.hashstore.1");
}

/**
* Confirm that deleteObject deletes object and empty sub directories
* Confirm that deleteObject throws exception when pid is null
*/
@Test(expected = IllegalArgumentException.class)
public void deleteObject_pidNull() throws Exception {
fileHashStore.deleteObject(null);
}

/**
* Confirm that deleteObject deletes object and empty sub directories
* Confirm that deleteObject throws exception when pid is empty
*/
@Test(expected = IllegalArgumentException.class)
public void deleteObject_pidEmpty() throws Exception {
fileHashStore.deleteObject("");
}

/**
* Confirm that deleteObject deletes object and empty sub directories
* Confirm that deleteObject throws exception when pid is empty spaces
*/
@Test(expected = IllegalArgumentException.class)
public void deleteObject_pidEmptySpaces() throws Exception {
fileHashStore.deleteObject(" ");
}

/**
* Confirm that deleteMetadata deletes object and empty sub directories
*/
@Test
public void deleteMetadata() throws Exception {
for (String pid : testData.pidList) {
String pidFormatted = pid.replace("/", "_");

// Get test metadata file
Path testMetaDataFile = testData.getTestFile(pidFormatted + ".xml");

InputStream metadataStream = Files.newInputStream(testMetaDataFile);
String metadataCid = fileHashStore.storeMetadata(metadataStream, pid, null);

String storeFormatId = (String) this.fhsProperties.get("storeMetadataNamespace");
boolean isMetadataDeleted = fileHashStore.deleteMetadata(pid, storeFormatId);
assertTrue(isMetadataDeleted);

// Double check that file doesn't exist
Path storePath = (Path) this.fhsProperties.get("storePath");
Path metadataStoreDirectory = storePath.resolve("metadata");
int storeDepth = (int) this.fhsProperties.get("storeDepth");
int storeWidth = (int) this.fhsProperties.get("storeWidth");
String metadataCidShardString = fileHashStore.getHierarchicalPathString(storeDepth, storeWidth,
metadataCid);
Path metadataCidPath = metadataStoreDirectory.resolve(metadataCidShardString);
assertFalse(Files.exists(metadataCidPath));

// Double check that metadata directory still exists
Path storeObjectPath = storePath.resolve("metadata");
assertTrue(Files.exists(storeObjectPath));
}
}

/**
* Confirm that deleteMetadata throws exception when associated pid obj not
* found
*/
@Test(expected = FileNotFoundException.class)
public void deleteMetadata_pidNotFound() throws Exception {
String formatId = "http://hashstore.tests/types/v1.0";
fileHashStore.deleteMetadata("dou.2023.hashstore.1", formatId);
}

/**
* Confirm that deleteMetadata throws exception when pid is null
*/
@Test(expected = IllegalArgumentException.class)
public void deleteMetadata_pidNull() throws Exception {
String formatId = "http://hashstore.tests/types/v1.0";
fileHashStore.deleteMetadata(null, formatId);
}

/**
* Confirm that deleteMetadata throws exception when pid is empty
*/
@Test(expected = IllegalArgumentException.class)
public void deleteMetadata_pidEmpty() throws Exception {
String formatId = "http://hashstore.tests/types/v1.0";
fileHashStore.deleteMetadata("", formatId);
}

/**
* Confirm that deleteMetadata throws exception when pid is empty spaces
*/
@Test(expected = IllegalArgumentException.class)
public void deleteMetadata_pidEmptySpaces() throws Exception {
String formatId = "http://hashstore.tests/types/v1.0";
fileHashStore.deleteMetadata(" ", formatId);
}

/**
* Confirm that deleteMetadata throws exception when formatId is null
*/
@Test(expected = IllegalArgumentException.class)
public void deleteMetadata_formatIdNull() throws Exception {
String pid = "dou.2023.hashstore.1";
fileHashStore.deleteMetadata(pid, null);
}

/**
* Confirm that deleteMetadata throws exception when formatId is empty
*/
@Test(expected = IllegalArgumentException.class)
public void deleteMetadata_formatIdEmpty() throws Exception {
String pid = "dou.2023.hashstore.1";
fileHashStore.deleteMetadata(pid, "");
}

/**
* Confirm that deleteMetadata throws exception when formatId is empty spaces
*/
@Test(expected = IllegalArgumentException.class)
public void deleteMetadata_formatIdEmptySpaces() throws Exception {
String pid = "dou.2023.hashstore.1";
fileHashStore.deleteMetadata(pid, " ");
}
}

0 comments on commit 9ac1933

Please sign in to comment.