Skip to content

Commit

Permalink
FilePartnerMetadataStorage implementation (#704)
Browse files Browse the repository at this point in the history
* Added initial implementation for localfile saving + added custom exception

* Added missing excpetion handling in SendOrderUseCase

* Updated Jackson formatter to handle Instant

* Added implementation for readMetadata + exception handling

* Added test coverage

* Added JavaDoc for PartnerMetadataException

* 672: Test PartnerMetadataException and PartnerMetadata

* Fixed sonarcloud security risk

---------

Co-authored-by: halprin <[email protected]>
  • Loading branch information
basiliskus and halprin authored Dec 5, 2023
1 parent 8bf21a8 commit 17ae3f7
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package gov.hhs.cdc.trustedintermediary.etor.metadata;

/** Custom exception class use to catch partner metadata exceptions */
public class PartnerMetadataException extends Exception {

public PartnerMetadataException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

/** Interface to store and retrieve our partner-facing metadata. */
public interface PartnerMetadataStorage {
PartnerMetadata readMetadata(String uniqueId);
PartnerMetadata readMetadata(String uniqueId) throws PartnerMetadataException;

/**
* This method will do "upserts". If the record doesn't exist, it is created. If the record
* exists, it is updated.
*
* @param metadata The metadata to save.
*/
void saveMetadata(PartnerMetadata metadata);
void saveMetadata(PartnerMetadata metadata) throws PartnerMetadataException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import gov.hhs.cdc.trustedintermediary.etor.metadata.EtorMetadataStep;
import gov.hhs.cdc.trustedintermediary.etor.metadata.PartnerMetadata;
import gov.hhs.cdc.trustedintermediary.etor.metadata.PartnerMetadataException;
import gov.hhs.cdc.trustedintermediary.etor.metadata.PartnerMetadataStorage;
import gov.hhs.cdc.trustedintermediary.wrappers.MetricMetadata;
import java.time.Instant;
Expand All @@ -25,7 +26,11 @@ public void convertAndSend(final Order<?> order) throws UnableToSendOrderExcepti
var partnerMetadata =
new PartnerMetadata(
"uniqueId", "senderName", "receiverName", Instant.now(), "abcd");
partnerMetadataStorage.saveMetadata(partnerMetadata);
try {
partnerMetadataStorage.saveMetadata(partnerMetadata);
} catch (PartnerMetadataException e) {
throw new UnableToSendOrderException("Unable to save metadata for the order", e);
}

var omlOrder = converter.convertMetadataToOmlOrder(order);
metadata.put(order.getFhirResourceId(), EtorMetadataStep.ORDER_CONVERTED_TO_OML);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,71 @@
package gov.hhs.cdc.trustedintermediary.external.localfile;

import gov.hhs.cdc.trustedintermediary.etor.metadata.PartnerMetadata;
import gov.hhs.cdc.trustedintermediary.etor.metadata.PartnerMetadataException;
import gov.hhs.cdc.trustedintermediary.etor.metadata.PartnerMetadataStorage;
import gov.hhs.cdc.trustedintermediary.wrappers.Logger;
import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter;
import gov.hhs.cdc.trustedintermediary.wrappers.formatter.FormatterProcessingException;
import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermissions;
import javax.inject.Inject;

/** Implements the {@link PartnerMetadataStorage} using local files. */
public class FilePartnerMetadataStorage implements PartnerMetadataStorage {

private static final FilePartnerMetadataStorage INSTANCE = new FilePartnerMetadataStorage();

@Inject Formatter formatter;
@Inject Logger logger;

private static final Path metadataTempDirectory;

static {
try {
FileAttribute<?> onlyOwnerAttrs =
PosixFilePermissions.asFileAttribute(
PosixFilePermissions.fromString("rwx------"));
metadataTempDirectory = Files.createTempDirectory("metadata", onlyOwnerAttrs);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

private FilePartnerMetadataStorage() {}

public static FilePartnerMetadataStorage getInstance() {
return INSTANCE;
}

@Override
public PartnerMetadata readMetadata(final String uniqueId) {
return null;
public PartnerMetadata readMetadata(final String uniqueId) throws PartnerMetadataException {
Path filePath = getFilePath(uniqueId);
try {
String content = Files.readString(filePath);
return formatter.convertJsonToObject(content, new TypeReference<>() {});
} catch (IOException | FormatterProcessingException e) {
throw new PartnerMetadataException("Unable to read the metadata file", e);
}
}

@Override
public void saveMetadata(final PartnerMetadata metadata) {}
public void saveMetadata(final PartnerMetadata metadata) throws PartnerMetadataException {
Path metadataFilePath = getFilePath(metadata.uniqueId());
try {
String content = formatter.convertToJsonString(metadata);
Files.writeString(metadataFilePath, content);
logger.logInfo("Saved metadata for " + metadata.uniqueId() + " to " + metadataFilePath);
} catch (IOException | FormatterProcessingException e) {
throw new PartnerMetadataException(
"Error saving metadata for " + metadata.uniqueId(), e);
}
}

private Path getFilePath(String uniqueId) {
return metadataTempDirectory.resolve(uniqueId + ".json");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package gov.hhs.cdc.trustedintermediary.etor.metadata


import spock.lang.Specification

class PartnerMetadataExceptionTest extends Specification {
def "constructor works"() {

given:
def message = "something blew up!"
def cause = new NullPointerException()

when:
def exception = new PartnerMetadataException(message, cause)

then:
exception.getMessage() == message
exception.getCause() == cause
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package gov.hhs.cdc.trustedintermediary.etor.metadata


import gov.hhs.cdc.trustedintermediary.PojoTestUtils
import java.time.Instant
import spock.lang.Specification

class PartnerMetadataTest extends Specification {
def "test getters and setters"() {
when:
PojoTestUtils.validateGettersAndSetters(PartnerMetadata)

then:
noExceptionThrown()
}

def "test constructor"() {
given:
def uniqueId = "uniqueId"
def sender = "sender"
def receiver = "receiver"
def timeReceived = Instant.now()
def hash = "abcd"

when:
def metadata = new PartnerMetadata(uniqueId, sender, receiver, timeReceived, hash)

then:
metadata.uniqueId() == uniqueId
metadata.sender() == sender
metadata.receiver() == receiver
metadata.timeReceived() == timeReceived
metadata.hash() == hash
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package gov.hhs.cdc.trustedintermediary.external.localfile

import gov.hhs.cdc.trustedintermediary.context.TestApplicationContext
import gov.hhs.cdc.trustedintermediary.etor.metadata.PartnerMetadata
import gov.hhs.cdc.trustedintermediary.etor.metadata.PartnerMetadataException
import gov.hhs.cdc.trustedintermediary.external.jackson.Jackson
import gov.hhs.cdc.trustedintermediary.wrappers.formatter.Formatter
import gov.hhs.cdc.trustedintermediary.wrappers.formatter.FormatterProcessingException
import gov.hhs.cdc.trustedintermediary.wrappers.formatter.TypeReference
import spock.lang.Specification

import java.time.Instant

class FilePartnerMetadataStorageTest extends Specification {

def setup() {
TestApplicationContext.reset()
TestApplicationContext.init()
TestApplicationContext.register(FilePartnerMetadataStorage, FilePartnerMetadataStorage.getInstance())
}

def "save and read metadata successfully"() {
given:
def expectedUniqueId = "uniqueId"
PartnerMetadata metadata = new PartnerMetadata(expectedUniqueId, "sender", "receiver", Instant.parse("2023-12-04T18:51:48.941875Z"), "abcd")

TestApplicationContext.register(Formatter, Jackson.getInstance())
TestApplicationContext.injectRegisteredImplementations()

when:
FilePartnerMetadataStorage.getInstance().saveMetadata(metadata)
def actualMetadata = FilePartnerMetadataStorage.getInstance().readMetadata(expectedUniqueId)

then:
actualMetadata == metadata
}

def "saveMetadata throws PartnerMetadataException when unable to save file"() {
given:
PartnerMetadata metadata = new PartnerMetadata("uniqueId", "sender", "receiver", Instant.now(), "abcd")

def mockFormatter = Mock(Formatter)
mockFormatter.convertToJsonString(_ as PartnerMetadata) >> {throw new FormatterProcessingException("error", new Exception())}
TestApplicationContext.register(Formatter, mockFormatter)
TestApplicationContext.injectRegisteredImplementations()

when:
FilePartnerMetadataStorage.getInstance().saveMetadata(metadata)

then:
thrown(PartnerMetadataException)
}

def "readMetadata throws PartnerMetadataException when unable to read file"() {
given:
def mockFormatter = Mock(Formatter)
mockFormatter.convertJsonToObject(_ as String, _ as TypeReference) >> {throw new FormatterProcessingException("error", new Exception())}
TestApplicationContext.register(Formatter, mockFormatter)
TestApplicationContext.injectRegisteredImplementations()

when:
FilePartnerMetadataStorage.getInstance().readMetadata("uniqueId")

then:
thrown(PartnerMetadataException)
}
}
1 change: 1 addition & 0 deletions shared/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies {
implementation 'com.fasterxml.jackson.core:jackson-core:2.16.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.16.0'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.16.0'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.16.0'

//fhir
api 'ca.uhn.hapi.fhir:hapi-fhir-base:6.10.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import gov.hhs.cdc.trustedintermediary.wrappers.Logger;
import gov.hhs.cdc.trustedintermediary.wrappers.YamlCombiner;
import gov.hhs.cdc.trustedintermediary.wrappers.YamlCombinerException;
Expand All @@ -30,6 +32,14 @@ public class Jackson implements Formatter, YamlCombiner {

@Inject Logger logger;

static {
JavaTimeModule javaTimeModule = new JavaTimeModule();
JSON_OBJECT_MAPPER.registerModule(javaTimeModule);
YAML_OBJECT_MAPPER.registerModule(javaTimeModule);
JSON_OBJECT_MAPPER.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
YAML_OBJECT_MAPPER.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}

private Jackson() {}

public static Jackson getInstance() {
Expand Down

0 comments on commit 17ae3f7

Please sign in to comment.