Skip to content
This repository has been archived by the owner on Jun 23, 2023. It is now read-only.

Commit

Permalink
API-3687 qualify references (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
that-guy-is-it authored Nov 25, 2020
1 parent d44f981 commit 2aa7fee
Show file tree
Hide file tree
Showing 4 changed files with 273 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package gov.va.api.health.patientgenerateddata;

import com.fasterxml.jackson.databind.ObjectMapper;
import gov.va.api.health.autoconfig.configuration.JacksonConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JacksonMapperConfig {

private final MagicReferenceConfig magicReferences;

@Autowired
public JacksonMapperConfig(MagicReferenceConfig magicReferences) {
this.magicReferences = magicReferences;
}

@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = JacksonConfig.createMapper();
return magicReferences.configure(mapper);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package gov.va.api.health.patientgenerateddata;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import gov.va.api.health.fhir.api.IsReference;
import gov.va.api.health.r4.api.elements.Reference;
import java.util.List;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class MagicReferenceConfig {
private final String baseUrl;

private final String r4BasePath;

@Autowired
public MagicReferenceConfig(
@Value("${public-url}") String baseUrl, @Value("${public-r4-base-path}") String r4BasePath) {
this.baseUrl = baseUrl;
this.r4BasePath = r4BasePath;
}

/** Configures and returns the mapper to support magic references. */
public ObjectMapper configure(ObjectMapper mapper) {
mapper.registerModule(new MagicReferenceModule());
return mapper;
}

private final class QualifiedReferenceWriter extends BeanPropertyWriter {

private QualifiedReferenceWriter(BeanPropertyWriter base) {
super(base);
}

private String qualify(String reference) {
if (StringUtils.isBlank(reference)) {
return null;
}
if (reference.startsWith("http")) {
return reference;
}
if (reference.startsWith("/")) {
return baseUrl + "/" + r4BasePath + reference;
}
return baseUrl + "/" + r4BasePath + "/" + reference;
}

@Override
@SneakyThrows
public void serializeAsField(
Object shouldBeReference, JsonGenerator gen, SerializerProvider prov) {
if (!(shouldBeReference instanceof IsReference)) {
throw new IllegalArgumentException(
"Qualified Reference writer cannot serialize: " + shouldBeReference);
}
IsReference reference = (IsReference) shouldBeReference;
String qualifiedReference = qualify(reference.reference());
if (qualifiedReference != null) {
gen.writeStringField(getName(), qualifiedReference);
}
}
}

private final class MagicReferenceModule extends SimpleModule {
@Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.addBeanSerializerModifier(
new BeanSerializerModifier() {
private void applyReferenceWriter(List<BeanPropertyWriter> beanProperties) {
for (int i = 0; i < beanProperties.size(); i++) {
BeanPropertyWriter beanPropertyWriter = beanProperties.get(i);
if ("reference".equals(beanPropertyWriter.getName())) {
beanProperties.set(i, new QualifiedReferenceWriter(beanPropertyWriter));
}
}
}

@Override
public List<BeanPropertyWriter> changeProperties(
SerializationConfig serialConfig,
BeanDescription beanDesc,
List<BeanPropertyWriter> beanProperties) {
if (beanDesc.getBeanClass() == Reference.class) {
applyReferenceWriter(beanProperties);
}
return super.changeProperties(serialConfig, beanDesc, beanProperties);
}
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package gov.va.api.health.patientgenerateddata;

import static org.assertj.core.api.Assertions.assertThat;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import gov.va.api.health.autoconfig.configuration.JacksonConfig;
import gov.va.api.health.r4.api.DataAbsentReason;
import gov.va.api.health.r4.api.elements.Extension;
import gov.va.api.health.r4.api.elements.Reference;
import java.util.List;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Singular;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;

public class JacksonMapperConfigTest {
private static Reference reference(String path) {
return Reference.builder().display("display-value").reference(path).id("id-value").build();
}

@Test
@SneakyThrows
public void preExistingDarsArePreserved() {
FugaziReferenceMajig input =
FugaziReferenceMajig.builder()
.ref(reference("https://example.com/api/Practitioner/1234"))
.nope(null)
._nope(DataAbsentReason.of(DataAbsentReason.Reason.error))
.build();
FugaziReferenceMajig expected =
FugaziReferenceMajig.builder()
.ref(reference("https://example.com/api/Practitioner/1234"))
.nope(null)
._nope(DataAbsentReason.of(DataAbsentReason.Reason.error))
.build();
String serializedJson =
new JacksonMapperConfig(new MagicReferenceConfig("https://example.com", "r4"))
.objectMapper()
.writerWithDefaultPrettyPrinter()
.writeValueAsString(input);
FugaziReferenceMajig actual =
JacksonConfig.createMapper().readValue(serializedJson, FugaziReferenceMajig.class);
assertThat(actual).isEqualTo(expected);
}

@Test
@SneakyThrows
public void referencesAreQualified() {
FugaziReferenceMajig input =
FugaziReferenceMajig.builder()
.whocares("noone")
.me(true)
.ref(reference("AllergyIntolerance/1234"))
.nope(reference("https://example.com/r4/Location/1234"))
._nope(DataAbsentReason.of(DataAbsentReason.Reason.unsupported))
.alsoNo(reference("https://example.com/r4/Location/1234"))
.thing(reference(null))
.thing(reference(""))
.thing(reference("http://qualified.is.not/touched"))
.thing(reference("no/slash"))
.thing(reference("/cool/a/slash"))
.thing(reference("Location"))
.thing(reference("Location/1234"))
.thing(reference("https://example.com/r4/Location/1234"))
.thing(reference("/Organization"))
.thing(reference("Organization/1234"))
.thing(reference("https://example.com/r4/Organization/1234"))
.thing(reference("Practitioner/987"))
.inner(
FugaziReferenceMajig.builder()
.ref(
Reference.builder()
.reference("Practitioner/615f31df-f0c7-5100-ac42-7fb952c630d0")
.display(null)
.build())
.build())
.build();
FugaziReferenceMajig expected =
FugaziReferenceMajig.builder()
.whocares("noone")
.me(true)
.nope(reference("https://example.com/r4/Location/1234"))
._nope(DataAbsentReason.of(DataAbsentReason.Reason.unsupported))
.alsoNo(reference("https://example.com/r4/Location/1234"))
.ref(reference("https://example.com/r4/AllergyIntolerance/1234"))
.thing(reference(null))
.thing(reference(null))
.thing(reference("http://qualified.is.not/touched"))
.thing(reference("https://example.com/r4/no/slash"))
.thing(reference("https://example.com/r4/cool/a/slash"))
.thing(reference("https://example.com/r4/Location"))
.thing(reference("https://example.com/r4/Location/1234"))
.thing(reference("https://example.com/r4/Location/1234"))
.thing(reference("https://example.com/r4/Organization"))
.thing(reference("https://example.com/r4/Organization/1234"))
.thing(reference("https://example.com/r4/Organization/1234"))
.thing(reference("https://example.com/r4/Practitioner/987"))
.inner(
FugaziReferenceMajig.builder()
.ref(
Reference.builder()
.reference(
"https://example.com/r4/Practitioner/615f31df-f0c7-5100-ac42-7fb952c630d0")
.build())
.build())
.build();
String qualifiedJson =
new JacksonMapperConfig(new MagicReferenceConfig("https://example.com", "r4"))
.objectMapper()
.writerWithDefaultPrettyPrinter()
.writeValueAsString(input);
FugaziReferenceMajig actual =
JacksonConfig.createMapper().readValue(qualifiedJson, FugaziReferenceMajig.class);
assertThat(actual).isEqualTo(expected);
}

@Data
@Builder
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor
@JsonAutoDetect(
fieldVisibility = JsonAutoDetect.Visibility.ANY,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
static final class FugaziReferenceMajig {
Reference ref;

Reference nope;

Extension _nope;

Reference alsoNo;

@Singular List<Reference> things;

JacksonMapperConfigTest.FugaziReferenceMajig inner;

String whocares;

Boolean me;
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
public-r4-base-path=unset
public-url=unset
spring.datasource.url=jdbc:h2:.
ssl.enable-client=false
web-exception-key=test

0 comments on commit 2aa7fee

Please sign in to comment.