Cleanup and utilities from composite work (#4066)

Utilities and cleanup from composite work #4066
This commit is contained in:
michaelabuckley 2022-09-23 20:47:25 -04:00 committed by GitHub
parent 6f80206776
commit ee1cb4e392
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 327 additions and 180 deletions

View File

@ -3,6 +3,9 @@ package ca.uhn.fhir.util;
import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.i18n.Msg;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.util.Objects;
import java.util.Optional;
/* /*
* #%L * #%L
* HAPI FHIR - Core Library * HAPI FHIR - Core Library
@ -25,14 +28,12 @@ import org.apache.commons.lang3.StringUtils;
public class ObjectUtil { public class ObjectUtil {
/**
* @deprecated Just use Objects.equals() instead;
*/
@Deprecated
public static boolean equals(Object object1, Object object2) { public static boolean equals(Object object1, Object object2) {
if (object1 == object2) { return Objects.equals(object1, object2);
return true;
}
if ((object1 == null) || (object2 == null)) {
return false;
}
return object1.equals(object2);
} }
public static <T> T requireNonNull(T obj, String message) { public static <T> T requireNonNull(T obj, String message) {
@ -46,5 +47,21 @@ public class ObjectUtil {
throw new IllegalArgumentException(Msg.code(1777) + message); throw new IllegalArgumentException(Msg.code(1777) + message);
} }
} }
/**
* Cast the object to the type using Optional.
* Useful for streaming with flatMap.
* @param theObject any object
* @param theClass the class to check instanceof
* @return Optional present if theObject is of type theClass
*/
@SuppressWarnings("unchecked")
public static <T> Optional<T> castIfInstanceof(Object theObject, Class<T> theClass) {
if (theClass.isInstance(theObject)) {
return Optional.of((T) theObject);
} else {
return Optional.empty();
}
}
} }

View File

@ -1,16 +1,22 @@
package ca.uhn.fhir.util; package ca.uhn.fhir.util;
import static org.junit.jupiter.api.Assertions.*;
import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.i18n.Msg;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
public class ObjectUtilTest { import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
class ObjectUtilTest {
@Test @Test
public void testEquals() { void testEquals() {
String a = new String("a"); String a = "a";
String b = new String("b"); String b = "b";
assertFalse(ObjectUtil.equals(b, a)); assertFalse(ObjectUtil.equals(b, a));
assertFalse(ObjectUtil.equals(a, b)); assertFalse(ObjectUtil.equals(a, b));
assertFalse(ObjectUtil.equals(a, null)); assertFalse(ObjectUtil.equals(a, null));
@ -20,7 +26,7 @@ public class ObjectUtilTest {
} }
@Test @Test
public void testRequireNonNull() { void testRequireNonNull() {
String message = "Must not be null in test"; String message = "Must not be null in test";
try { try {
ObjectUtil.requireNonNull(null, message); ObjectUtil.requireNonNull(null, message);
@ -32,7 +38,7 @@ public class ObjectUtilTest {
} }
@Test @Test
public void testRequireNotEmpty() { void testRequireNotEmpty() {
//All these are empty, null or whitespace strings. //All these are empty, null or whitespace strings.
testRequireNotEmptyErrorScenario(null); testRequireNotEmptyErrorScenario(null);
testRequireNotEmptyErrorScenario(""); testRequireNotEmptyErrorScenario("");
@ -54,5 +60,22 @@ public class ObjectUtilTest {
assertEquals(Msg.code(1777) + message, e.getMessage()); assertEquals(Msg.code(1777) + message, e.getMessage());
} }
} }
@Test
void testCast_isInstance_present() {
Boolean value = Boolean.FALSE;
Optional<Boolean> result = ObjectUtil.castIfInstanceof(value, Boolean.class);
assertTrue(result.isPresent());
}
@Test
void testCast_isNotInstance_empty() {
Boolean value = Boolean.FALSE;
Optional<Integer> result = ObjectUtil.castIfInstanceof(value, Integer.class);
assertTrue(result.isEmpty());
}
} }

View File

@ -284,14 +284,13 @@ public class TestUtil {
} else { } else {
Validate.notNull(fk); Validate.notNull(fk);
Validate.isTrue(isNotBlank(fk.name()), "Foreign key on " + theAnnotatedElement + " has no name()"); Validate.isTrue(isNotBlank(fk.name()), "Foreign key on " + theAnnotatedElement + " has no name()");
// temporarily allow the hibernate legacy sp fk names
List<String> legacySPHibernateFKNames = Arrays.asList( List<String> legacySPHibernateFKNames = Arrays.asList(
"FKC97MPK37OKWU8QVTCEG2NH9VN", "FKGXSREUTYMMFJUWDSWV3Y887DO"); "FKC97MPK37OKWU8QVTCEG2NH9VN", "FKGXSREUTYMMFJUWDSWV3Y887DO");
if (legacySPHibernateFKNames.contains(fk.name())) { Validate.isTrue(fk.name().startsWith("FK_") || legacySPHibernateFKNames.contains(fk.name()),
// wipmb temporarily allow the hibernate legacy sp fk names "Foreign key " + fk.name() + " on " + theAnnotatedElement + " must start with FK");
} else {
Validate.isTrue(fk.name().startsWith("FK_"),
"Foreign key " + fk.name() + " on " + theAnnotatedElement + " must start with FK");
}
if ( ! duplicateNameValidationExceptionList.contains(fk.name())) { if ( ! duplicateNameValidationExceptionList.contains(fk.name())) {
assertNotADuplicateName(fk.name(), theNames); assertNotADuplicateName(fk.name(), theNames);
} }

View File

@ -368,7 +368,6 @@ public class SearchBuilder implements ISearchBuilder {
} }
if (theSearchRuntimeDetails != null) { if (theSearchRuntimeDetails != null) {
// wipmb we no longer have full size.
theSearchRuntimeDetails.setFoundIndexMatchesCount(resultCount); theSearchRuntimeDetails.setFoundIndexMatchesCount(resultCount);
HookParams params = new HookParams() HookParams params = new HookParams()
.add(RequestDetails.class, theRequest) .add(RequestDetails.class, theRequest)
@ -377,7 +376,7 @@ public class SearchBuilder implements ISearchBuilder {
CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_INDEXSEARCH_QUERY_COMPLETE, params); CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_INDEXSEARCH_QUERY_COMPLETE, params);
} }
// wipmb extract // todo MB extract this and move to FullText svc
// can we skip the database entirely and return the pid list from here? // can we skip the database entirely and return the pid list from here?
boolean canSkipDatabase = boolean canSkipDatabase =
// if we processed an AND clause, and it returned nothing, then nothing can match. // if we processed an AND clause, and it returned nothing, then nothing can match.
@ -388,12 +387,10 @@ public class SearchBuilder implements ISearchBuilder {
theParams.isEmpty() && theParams.isEmpty() &&
// not every param is a param. :-( // not every param is a param. :-(
theParams.getNearDistanceParam() == null && theParams.getNearDistanceParam() == null &&
// todo MB don't we support _lastUpdated and _offset now?
theParams.getLastUpdated() == null && theParams.getLastUpdated() == null &&
theParams.getEverythingMode() == null && theParams.getEverythingMode() == null &&
theParams.getOffset() == null theParams.getOffset() == null
// &&
// // or sorting?
// theParams.getSort() == null
); );
if (canSkipDatabase) { if (canSkipDatabase) {
@ -406,7 +403,6 @@ public class SearchBuilder implements ISearchBuilder {
ourLog.trace("Query needs db after HSearch. Chunking."); ourLog.trace("Query needs db after HSearch. Chunking.");
// Finish the query in the database for the rest of the search parameters, sorting, partitioning, etc. // Finish the query in the database for the rest of the search parameters, sorting, partitioning, etc.
// We break the pids into chunks that fit in the 1k limit for jdbc bind params. // We break the pids into chunks that fit in the 1k limit for jdbc bind params.
// wipmb change chunk to take iterator
new QueryChunker<Long>() new QueryChunker<Long>()
.chunk(Streams.stream(fulltextExecutor).collect(Collectors.toList()), t -> doCreateChunkedQueries(theParams, t, theOffset, sort, theCountOnlyFlag, theRequest, queries)); .chunk(Streams.stream(fulltextExecutor).collect(Collectors.toList()), t -> doCreateChunkedQueries(theParams, t, theOffset, sort, theCountOnlyFlag, theRequest, queries));
} }
@ -1016,7 +1012,6 @@ public class SearchBuilder implements ISearchBuilder {
if (myDaoConfig.isAdvancedHSearchIndexing() && myDaoConfig.isStoreResourceInHSearchIndex()) { if (myDaoConfig.isAdvancedHSearchIndexing() && myDaoConfig.isStoreResourceInHSearchIndex()) {
List<Long> pidList = thePids.stream().map(ResourcePersistentId::getIdAsLong).collect(Collectors.toList()); List<Long> pidList = thePids.stream().map(ResourcePersistentId::getIdAsLong).collect(Collectors.toList());
// wipmb standardize on ResourcePersistentId
List<IBaseResource> resources = myFulltextSearchSvc.getResources(pidList); List<IBaseResource> resources = myFulltextSearchSvc.getResources(pidList);
return resources; return resources;
} else if (!Objects.isNull(myParams) && myParams.isLastN()) { } else if (!Objects.isNull(myParams) && myParams.isLastN()) {
@ -1693,7 +1688,6 @@ public class SearchBuilder implements ISearchBuilder {
private void initializeIteratorQuery(Integer theOffset, Integer theMaxResultsToFetch) { private void initializeIteratorQuery(Integer theOffset, Integer theMaxResultsToFetch) {
if (myQueryList.isEmpty()) { if (myQueryList.isEmpty()) {
// wipmb what is this?
// Capture times for Lucene/Elasticsearch queries as well // Capture times for Lucene/Elasticsearch queries as well
mySearchRuntimeDetails.setQueryStopwatch(new StopWatch()); mySearchRuntimeDetails.setQueryStopwatch(new StopWatch());
myQueryList = createQuery(myParams, mySort, theOffset, theMaxResultsToFetch, false, myRequest, mySearchRuntimeDetails); myQueryList = createQuery(myParams, mySort, theOffset, theMaxResultsToFetch, false, myRequest, mySearchRuntimeDetails);

View File

@ -65,7 +65,7 @@ abstract public class BaseR4SearchLastN extends BaseJpaTest {
private static final Map<String, String> observationCategoryMap = new HashMap<>(); private static final Map<String, String> observationCategoryMap = new HashMap<>();
private static final Map<String, String> observationCodeMap = new HashMap<>(); private static final Map<String, String> observationCodeMap = new HashMap<>();
private static final Map<String, Date> observationEffectiveMap = new HashMap<>(); private static final Map<String, Date> observationEffectiveMap = new HashMap<>();
// wipmb make thise normal fields. This static setup wasn't working // todo mb make thise normal fields. This static setup wasn't working
protected static IIdType patient0Id = null; protected static IIdType patient0Id = null;
protected static IIdType patient1Id = null; protected static IIdType patient1Id = null;
protected static IIdType patient2Id = null; protected static IIdType patient2Id = null;

View File

@ -1,68 +0,0 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] %msg%n</pattern>
</encoder>
</appender>
<!--<logger name="ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionMatchingSubscriber" additivity="false" level="trace">
<appender-ref ref="STDOUT" />
</logger>-->
<logger name="org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="ca.uhn.fhir.jpa.dao.FhirResourceDaoSubscriptionDstu2" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.eclipse.jetty.websocket" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.hibernate.event.internal.DefaultPersistEventListener" additivity="true" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.eclipse" additivity="false" level="error">
</logger>
<logger name="ca.uhn.fhir.rest.client" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="ca.uhn.fhir.jpa.dao" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<!-- set to debug to enable term expansion logs -->
<logger name="ca.uhn.fhir.jpa.term" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<!-- Set to 'trace' to enable SQL logging -->
<logger name="org.hibernate.SQL" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<!-- Set to 'trace' to enable SQL Value logging -->
<logger name="org.hibernate.type" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<!-- <logger name="ca.uhn.fhir.jpa.model.search" additivity="false" level="debug"/>-->
<!-- <logger name="org.elasticsearch.client" additivity="true" level="trace"/>-->
<!-- <logger name="org.hibernate.search.elasticsearch.request" additivity="false" level="trace"/>-->
<!-- <logger name="org.hibernate.search" level="TRACE"/>-->
<!-- <logger name="org.hibernate.search.query" level="TRACE"/>-->
<!-- <logger name="org.hibernate.search.elasticsearch.request" level="TRACE"/>-->
<!-- See https://docs.jboss.org/hibernate/stable/search/reference/en-US/html_single/#backend-lucene-io-writer-infostream for lucene logging
<logger name="org.hibernate.search.backend.lucene.infostream" level="TRACE"/> -->
<logger name="org.springframework.test.context.cache" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>

View File

@ -98,8 +98,10 @@ public class TestHSearchAddInConfig {
luceneProperties.put(BackendSettings.backendKey(LuceneIndexSettings.IO_WRITER_INFOSTREAM), "true"); luceneProperties.put(BackendSettings.backendKey(LuceneIndexSettings.IO_WRITER_INFOSTREAM), "true");
luceneProperties.put(HibernateOrmMapperSettings.ENABLED, "true"); luceneProperties.put(HibernateOrmMapperSettings.ENABLED, "true");
return (theProperties) -> return (theProperties) -> {
ourLog.debug("Configuring Hibernate Search - {}", luceneProperties);
theProperties.putAll(luceneProperties); theProperties.putAll(luceneProperties);
};
} }

View File

@ -5,71 +5,42 @@
</encoder> </encoder>
</appender> </appender>
<!--<logger name="ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionMatchingSubscriber" additivity="false" level="trace"> <!-- define the root first, so the rest can inherit our logger -->
<appender-ref ref="STDOUT" />
</logger>-->
<logger name="org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="ca.uhn.fhir.jpa.dao.FhirResourceDaoSubscriptionDstu2" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.eclipse.jetty.websocket" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.hibernate.event.internal.DefaultPersistEventListener" additivity="true" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.eclipse" additivity="false" level="error">
</logger>
<logger name="ca.uhn.fhir.rest.client" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="ca.uhn.fhir.jpa.dao" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<!-- set to debug to enable term expansion logs -->
<logger name="ca.uhn.fhir.jpa.term" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<!-- Set to 'trace' to enable SQL logging -->
<logger name="org.hibernate.SQL" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<!-- Set to 'trace' to enable SQL Value logging -->
<logger name="org.hibernate.type" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="org.elasticsearch.client" additivity="true" level="trace"/>
<!--
<logger name="ca.uhn.fhir.jpa.model.search" additivity="false" level="debug"/>
<logger name="org.elasticsearch.client" additivity="true" level="trace"/>
<logger name="org.hibernate.search" additivity="false" level="TRACE"/>
<logger name="org.hibernate.search.query" additivity="false" level="TRACE"/>
<logger name="org.hibernate.search.elasticsearch.request" additivity="false" level="TRACE"/>
-->
<!-- See https://docs.jboss.org/hibernate/stable/search/reference/en-US/html_single/#backend-lucene-io-writer-infostream for lucene logging
<logger name="org.hibernate.search.backend.lucene.infostream" level="TRACE"/> -->
<logger name="org.springframework.test.context.cache" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="ca.uhn.fhir.jpa.bulk" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<root level="info"> <root level="info">
<appender-ref ref="STDOUT" /> <appender-ref ref="STDOUT" />
</root> </root>
<logger name="ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionMatchingSubscriber" level="info"/>
<logger name="org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator" level="info"/>
<logger name="ca.uhn.fhir.jpa.dao.FhirResourceDaoSubscriptionDstu2" level="info"/>
<logger name="org.eclipse.jetty.websocket" level="info"/>
<logger name="org.hibernate.event.internal.DefaultPersistEventListener" level="info"/>
<logger name="org.eclipse" level="error"/>
<logger name="ca.uhn.fhir.rest.client" level="info"/>
<logger name="ca.uhn.fhir.jpa.dao" level="info"/>
<!-- set to debug to enable term expansion logs -->
<logger name="ca.uhn.fhir.jpa.term" level="info"/>
<!-- Set to 'trace' to enable SQL logging -->
<logger name="org.hibernate.SQL" level="info"/>
<!-- Set to 'trace' to enable SQL Value logging -->
<logger name="org.hibernate.type" level="info"/>
<logger name="org.springframework.test.context.cache" level="info"/>
<logger name="ca.uhn.fhir.jpa.bulk" level="info"/>
<!-- debugging -->
<!--
<logger name="org.elasticsearch.client" level="trace"/>
<logger name="org.hibernate.search.elasticsearch.request" level="TRACE"/>
<logger name="ca.uhn.fhir.jpa.model.search" level="debug"/>
<logger name="org.elasticsearch.client" level="trace"/>
<logger name="org.hibernate.search" level="debug"/>
<logger name="org.hibernate.search.query" level="TRACE"/>
<logger name="org.hibernate.search.elasticsearch.request" level="TRACE"/>
-->
<!-- See https://docs.jboss.org/hibernate/stable/search/reference/en-US/html_single/#backend-lucene-io-writer-infostream for lucene logging
<logger name="org.hibernate.search.backend.lucene.infostream" level="TRACE"/> -->
</configuration> </configuration>

View File

@ -0,0 +1,123 @@
package ca.uhn.fhir.test.utilities;
import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Quantity;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class ITestDataBuilderTest {
FhirContext myFhirContext = FhirContext.forR4Cached();
List<IBaseResource> myCreatedList = new ArrayList<>();
List<IBaseResource> myUpdatedList = new ArrayList<>();
ITestDataBuilder myTDB = new ITestDataBuilder() {
@Override
public IIdType doCreateResource(IBaseResource theResource) {
myCreatedList.add(theResource);
return null;
}
@Override
public IIdType doUpdateResource(IBaseResource theResource) {
myUpdatedList.add(theResource);
return theResource.getIdElement();
}
@Override
public FhirContext getFhirContext() {
return myFhirContext;
}
};
@Nested
class ObservationCreation {
@Test
void createObservation_withEffective_setsDate() {
myTDB.createObservation(
myTDB.withEffectiveDate("2020-01-01T12:34:56"));
assertEquals(1, myCreatedList.size());
Observation o = (Observation) myCreatedList.get(0);
assertEquals("2020-01-01T12:34:56", o.getEffectiveDateTimeType().getValueAsString());
}
@Test
void createObservation_withObservationCode_setsCode() {
// when
myTDB.createObservation(
myTDB.withObservationCode("http://example.com", "a-code-value", "a code description")
);
assertEquals(1, myCreatedList.size());
Observation o = (Observation) myCreatedList.get(0);
CodeableConcept codeable = o.getCode();
assertNotNull(codeable);
assertEquals(1,codeable.getCoding().size(), "has one coding");
Coding coding = codeable.getCoding().get(0);
assertEquals("http://example.com", coding.getSystem());
assertEquals("a-code-value", coding.getCode());
assertEquals("a code description", coding.getDisplay());
}
@Test
void createObservation_withValueQuantity_createsQuantity() {
myTDB.createObservation(
myTDB.withQuantityAtPath("valueQuantity", 200, "hulla", "bpm"));
assertEquals(1, myCreatedList.size());
Observation o = (Observation) myCreatedList.get(0);
Quantity valueQuantity = o.getValueQuantity();
assertNotNull(valueQuantity);
assertEquals(200, valueQuantity.getValue().doubleValue());
assertEquals("hulla", valueQuantity.getSystem());
assertEquals("bpm", valueQuantity.getCode());
}
@Test
void createObservation_withComponents_createsComponents() {
// when
myTDB.createObservation(
myTDB.withObservationCode("http://example.com", "a-code-value", "a code description"),
myTDB.withEffectiveDate("2020-01-01T12:34:56"),
myTDB.withObservationComponent(
myTDB.withObservationCode("http://example.com", "another-code-value"),
myTDB.withQuantityAtPath("valueQuantity", 200, "hulla", "bpm")),
myTDB.withObservationComponent(
myTDB.withObservationCode("http://example.com", "yet-another-code-value"),
myTDB.withQuantityAtPath("valueQuantity", 1000000, "hulla", "sik"))
);
assertEquals(1, myCreatedList.size());
Observation o = (Observation) myCreatedList.get(0);
assertEquals(2, o.getComponent().size());
Observation.ObservationComponentComponent secondComponent = o.getComponent().get(1);
assertEquals("yet-another-code-value", secondComponent.getCode().getCoding().get(0).getCode());
assertEquals(1000000.0, secondComponent.getValueQuantity().getValue().doubleValue());
}
}
}

View File

@ -0,0 +1,4 @@
/**
* Some test for our utilities that need a FhirContext
*/
package ca.uhn.fhir.test.utilities;

View File

@ -84,7 +84,12 @@ public class FhirContextSearchParamRegistry implements ISearchParamRegistry {
@Nullable @Nullable
@Override @Override
public RuntimeSearchParam getActiveSearchParamByUrl(String theUrl) { public RuntimeSearchParam getActiveSearchParamByUrl(String theUrl) {
throw new UnsupportedOperationException(Msg.code(2067)); // simple implementation for test support
return myCtx.getResourceTypes().stream()
.flatMap(type->getActiveSearchParams(type).values().stream())
.filter(rsp->theUrl.equals(rsp.getUri()))
.findFirst()
.orElse(null);
} }
@Override @Override

View File

@ -26,7 +26,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.util.FhirTerser; import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.MetaUtil; import ca.uhn.fhir.util.MetaUtil;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
@ -34,6 +34,8 @@ import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.InstantType; import org.hl7.fhir.r4.model.InstantType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Collection; import java.util.Collection;
@ -49,6 +51,7 @@ import static org.hamcrest.Matchers.matchesPattern;
*/ */
@SuppressWarnings({"unchecked", "ConstantConditions"}) @SuppressWarnings({"unchecked", "ConstantConditions"})
public interface ITestDataBuilder { public interface ITestDataBuilder {
Logger ourLog = LoggerFactory.getLogger(ITestDataBuilder.class);
/** /**
* Set Patient.active = true * Set Patient.active = true
@ -106,6 +109,13 @@ public interface ITestDataBuilder {
return t -> __setPrimitiveChild(getFhirContext(), t, "effectiveDateTime", "dateTime", theDate); return t -> __setPrimitiveChild(getFhirContext(), t, "effectiveDateTime", "dateTime", theDate);
} }
/**
* Set Observation.effectiveDate
*/
default Consumer<IBaseResource> withDateTimeAt(String thePath, String theDate) {
return t -> __setPrimitiveChild(getFhirContext(), t, thePath, "dateTime", theDate);
}
/** /**
* Set [Resource].identifier.system and [Resource].identifier.value * Set [Resource].identifier.system and [Resource].identifier.value
*/ */
@ -192,6 +202,10 @@ public interface ITestDataBuilder {
default IIdType createResource(String theResourceType, Consumer<IBaseResource>... theModifiers) { default IIdType createResource(String theResourceType, Consumer<IBaseResource>... theModifiers) {
IBaseResource resource = buildResource(theResourceType, theModifiers); IBaseResource resource = buildResource(theResourceType, theModifiers);
if (ourLog.isDebugEnabled()) {
ourLog.debug("Creating {}", getFhirContext().newJsonParser().encodeResourceToString(resource));
}
if (isNotBlank(resource.getIdElement().getValue())) { if (isNotBlank(resource.getIdElement().getValue())) {
return doUpdateResource(resource); return doUpdateResource(resource);
} else { } else {
@ -227,15 +241,15 @@ public interface ITestDataBuilder {
}; };
} }
default <T extends IBase> Consumer<T> withElementAt(String thePath, Consumer<IBase>... theModifiers) { default <T extends IBase, E extends IBase> Consumer<T> withElementAt(String thePath, Consumer<E>... theModifiers) {
return t->{ return t->{
FhirTerser terser = getFhirContext().newTerser(); FhirTerser terser = getFhirContext().newTerser();
IBase element = terser.addElement(t, thePath); E element = terser.addElement(t, thePath);
applyElementModifiers(element, theModifiers); applyElementModifiers(element, theModifiers);
}; };
} }
default Consumer<IBaseResource> withQuantityAtPath(String thePath, double theValue, String theSystem, String theCode) { default <T extends IBase> Consumer<T> withQuantityAtPath(String thePath, Number theValue, String theSystem, String theCode) {
return withElementAt(thePath, return withElementAt(thePath,
withPrimitiveAttribute("value", theValue), withPrimitiveAttribute("value", theValue),
withPrimitiveAttribute("system", theSystem), withPrimitiveAttribute("system", theSystem),
@ -256,8 +270,8 @@ public interface ITestDataBuilder {
return element; return element;
} }
default void applyElementModifiers(IBase element, Consumer<IBase>[] theModifiers) { default <E extends IBase> void applyElementModifiers(E element, Consumer<E>[] theModifiers) {
for (Consumer<IBase> nextModifier : theModifiers) { for (Consumer<E> nextModifier : theModifiers) {
nextModifier.accept(element); nextModifier.accept(element);
} }
} }
@ -266,16 +280,24 @@ public interface ITestDataBuilder {
return withObservationCode(theSystem, theCode, null); return withObservationCode(theSystem, theCode, null);
} }
default Consumer<IBaseResource> withObservationCode(@Nullable String theSystem, @Nullable String theCode, String theDisplay) { default Consumer<IBaseResource> withObservationCode(@Nullable String theSystem, @Nullable String theCode, @Nullable String theDisplay) {
return t -> { return withCodingAt("code.coding", theSystem, theCode, theDisplay);
FhirTerser terser = getFhirContext().newTerser(); }
IBase coding = terser.addElement(t, "code.coding");
terser.addElement(coding, "system", theSystem); default <T extends IBase> Consumer<T> withCodingAt(String thePath, @Nullable String theSystem, @Nullable String theValue) {
terser.addElement(coding, "code", theCode); return withCodingAt(thePath, theSystem, theValue, null);
if (StringUtils.isNotEmpty(theDisplay)) { }
terser.addElement(coding, "display", theDisplay);
} default <T extends IBase> Consumer<T> withCodingAt(String thePath, @Nullable String theSystem, @Nullable String theValue, @Nullable String theDisplay) {
}; return withElementAt(thePath,
withPrimitiveAttribute("system", theSystem),
withPrimitiveAttribute("code", theValue),
withPrimitiveAttribute("display", theDisplay)
);
}
default <T extends IBaseResource, E extends IBase> Consumer<T> withObservationComponent(Consumer<E>... theModifiers) {
return withElementAt("component", theModifiers);
} }
default Consumer<IBaseResource> withObservationHasMember(@Nullable IIdType theHasMember) { default Consumer<IBaseResource> withObservationHasMember(@Nullable IIdType theHasMember) {
@ -302,6 +324,7 @@ public interface ITestDataBuilder {
}; };
} }
// todo mb extract these to something like TestDataBuilderBacking. Maybe split out create* into child interface since people skip it.
/** /**
* Users of this API must implement this method * Users of this API must implement this method
*/ */
@ -328,4 +351,58 @@ public interface ITestDataBuilder {
booleanType.setValueAsString(theValue); booleanType.setValueAsString(theValue);
activeChild.getMutator().addValue(theTarget, booleanType); activeChild.getMutator().addValue(theTarget, booleanType);
} }
interface Support {
FhirContext getFhirContext();
IIdType createResource(IBaseResource theResource);
IIdType updateResource(IBaseResource theResource);
}
interface WithSupport extends ITestDataBuilder {
Support getSupport();
@Override
default FhirContext getFhirContext() {
return getSupport().getFhirContext();
}
@Override
default IIdType doCreateResource(IBaseResource theResource) {
return getSupport().createResource(theResource);
}
@Override
default IIdType doUpdateResource(IBaseResource theResource) {
return getSupport().updateResource(theResource);
}
}
/**
* Dummy support to use ITestDataBuilder as just a builder, not a DAO
* todo mb Maybe we should split out the builder into a super-interface and drop this?
*/
class SupportNoDao implements Support {
final FhirContext myFhirContext;
public SupportNoDao(FhirContext theFhirContext) {
myFhirContext = theFhirContext;
}
@Override
public FhirContext getFhirContext() {
return myFhirContext;
}
@Override
public IIdType createResource(IBaseResource theResource) {
Validate.isTrue(false, "Create not supported");
return null;
}
@Override
public IIdType updateResource(IBaseResource theResource) {
Validate.isTrue(false, "Update not supported");
return null;
}
}
} }