GraphQL Introspection Support (#3348)

* Start working on graphql Schema

* Start testing

* Work on introspection

* Work on introspection

* Use integers for date ordinals

* Add changelog

* GraphQL updates

* Ongoing wrk

* Cleanup

* Bump core lib

* Add changelog

* Clean up dependencies

* CLeanup

* Add missing message

* Test fix

* Change to force CI
This commit is contained in:
James Agnew 2022-02-22 09:48:57 -05:00 committed by GitHub
parent 1e2e17784a
commit cc44deee1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1082 additions and 360 deletions

View File

@ -25,7 +25,7 @@ public final class Msg {
/**
* IMPORTANT: Please update the following comment after you add a new code
* Last code value: 2034
* Last code value: 2036
*/
private Msg() {}

View File

@ -0,0 +1,24 @@
package ca.uhn.fhir.util;
import java.util.function.Supplier;
/**
* This is used to allow lazy parameter initialization for SLF4j - Hopefully
* a future version will allow lambda params
*/
public class MessageSupplier {
private Supplier<?> supplier;
public MessageSupplier(Supplier<?> supplier) {
this.supplier = supplier;
}
@Override
public String toString() {
return supplier.get().toString();
}
public static MessageSupplier msg(Supplier<?> supplier) {
return new MessageSupplier(supplier);
}
}

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.util;
* #L%
*/
import javax.annotation.Nonnull;
import java.io.CharArrayWriter;
import java.nio.charset.StandardCharsets;
import java.text.Normalizer;
@ -105,4 +106,13 @@ public class StringUtil {
return theString.substring(0, theString.offsetByCodePoints(0, theCodePointCount));
}
@Nonnull
public static String prependLineNumbers(@Nonnull String theInput) {
StringBuilder schemaOutput = new StringBuilder();
int index = 0;
for (String next : theInput.split("\\n")) {
schemaOutput.append(index++).append(": ").append(next.replace("\r", "")).append("\n");
}
return schemaOutput.toString();
}
}

View File

@ -0,0 +1,64 @@
package ca.uhn.fhir.util;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static ca.uhn.fhir.util.MessageSupplier.msg;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class MessageSupplierTest {
private static final Logger ourLog = LoggerFactory.getLogger(MessageSupplierTest.class);
private Appender myMockAppender;
private ch.qos.logback.classic.Logger myLoggerRoot;
@AfterEach
public void after() {
myLoggerRoot.detachAppender(myMockAppender);
}
@SuppressWarnings("unchecked")
@BeforeEach
public void before() {
/*
* This is a bit funky, but it's useful for verifying that the headers actually get logged
*/
myMockAppender = mock(Appender.class);
when(myMockAppender.getName()).thenReturn("MOCK");
Logger logger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
myLoggerRoot = (ch.qos.logback.classic.Logger) logger;
myLoggerRoot.addAppender(myMockAppender);
}
@Test
public void testLog() {
ourLog.info("Hello: {}", msg(() -> "Goodbye"));
verify(myMockAppender, times(1)).doAppend(argThat((ArgumentMatcher<ILoggingEvent>) argument -> {
String formattedMessage = argument.getFormattedMessage();
System.out.flush();
System.out.println("** Got Message: " + formattedMessage);
System.out.flush();
return formattedMessage.equals("Hello: Goodbye");
}));
}
}

View File

@ -64,4 +64,9 @@ public class StringUtilTest {
assertEquals("a/a", StringUtil.chompCharacter("a/a////", '/'));
}
@Test
public void testPrependLineNumbers() {
assertEquals("0: A\n1: B\n", StringUtil.prependLineNumbers("A\nB"));
}
}

View File

@ -0,0 +1,5 @@
---
type: add
issue: 3348
title: "The HAPI FHIR server GraphQL endpoints now support GraphQL introspection, making them
much easier to use with GraphQL-capable IDEs."

View File

@ -15,24 +15,6 @@
<name>HAPI FHIR JPA Server</name>
<dependencies>
<!--
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate4</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
-->
<dependency>
<groupId>com.fasterxml.woodstox</groupId>
@ -174,6 +156,12 @@
<artifactId>javassist</artifactId>
</dependency>
<!-- GraphQL -->
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
</dependency>
<!-- SQL Builder -->
<dependency>
<groupId>com.healthmarketscience.sqlbuilder</groupId>

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.config.dstu3;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.IDaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.config.GeneratedDaoAndResourceProviderConfigDstu3;
import ca.uhn.fhir.jpa.config.JpaConfig;
@ -9,6 +10,7 @@ import ca.uhn.fhir.jpa.config.SharedConfigDstu3Plus;
import ca.uhn.fhir.jpa.dao.ITransactionProcessorVersionAdapter;
import ca.uhn.fhir.jpa.dao.dstu3.TransactionProcessorVersionAdapterDstu3;
import ca.uhn.fhir.jpa.graphql.GraphQLProvider;
import ca.uhn.fhir.jpa.graphql.GraphQLProviderWithIntrospection;
import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl;
import ca.uhn.fhir.jpa.term.TermReadSvcDstu3;
import ca.uhn.fhir.jpa.term.TermVersionAdapterSvcDstu3;
@ -17,6 +19,7 @@ import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3;
import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Meta;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
@ -62,8 +65,8 @@ public class JpaDstu3Config {
@Bean(name = JpaConfig.GRAPHQL_PROVIDER_NAME)
@Lazy
public GraphQLProvider graphQLProvider(FhirContext theFhirContext, IGraphQLStorageServices theGraphqlStorageServices, IValidationSupport theValidationSupport) {
return new GraphQLProvider(theFhirContext, theValidationSupport, theGraphqlStorageServices);
public GraphQLProvider graphQLProvider(FhirContext theFhirContext, IGraphQLStorageServices theGraphqlStorageServices, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry, IDaoRegistry theDaoRegistry) {
return new GraphQLProviderWithIntrospection(theFhirContext, theValidationSupport, theGraphqlStorageServices, theSearchParamRegistry, theDaoRegistry);
}
@Bean

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.config.r4;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.IDaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.config.GeneratedDaoAndResourceProviderConfigR4;
import ca.uhn.fhir.jpa.config.JpaConfig;
@ -9,6 +10,7 @@ import ca.uhn.fhir.jpa.config.SharedConfigDstu3Plus;
import ca.uhn.fhir.jpa.dao.ITransactionProcessorVersionAdapter;
import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4;
import ca.uhn.fhir.jpa.graphql.GraphQLProvider;
import ca.uhn.fhir.jpa.graphql.GraphQLProviderWithIntrospection;
import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl;
import ca.uhn.fhir.jpa.term.TermReadSvcR4;
import ca.uhn.fhir.jpa.term.TermVersionAdapterSvcR4;
@ -17,6 +19,7 @@ import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4;
import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Meta;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
@ -26,6 +29,26 @@ import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed 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.
* #L%
*/
@Configuration
@EnableTransactionManagement
@Import({
@ -48,8 +71,8 @@ public class JpaR4Config {
@Bean(name = JpaConfig.GRAPHQL_PROVIDER_NAME)
@Lazy
public GraphQLProvider graphQLProvider(FhirContext theFhirContext, IGraphQLStorageServices theGraphqlStorageServices, IValidationSupport theValidationSupport) {
return new GraphQLProvider(theFhirContext, theValidationSupport, theGraphqlStorageServices);
public GraphQLProvider graphQLProvider(FhirContext theFhirContext, IGraphQLStorageServices theGraphqlStorageServices, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry, IDaoRegistry theDaoRegistry) {
return new GraphQLProviderWithIntrospection(theFhirContext, theValidationSupport, theGraphqlStorageServices, theSearchParamRegistry, theDaoRegistry);
}
@Bean(name = "mySystemDaoR4")
@ -77,23 +100,3 @@ public class JpaR4Config {
}
}
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed 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.
* #L%
*/

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.config.r5;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.IDaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.config.GeneratedDaoAndResourceProviderConfigR5;
import ca.uhn.fhir.jpa.config.JpaConfig;
@ -9,6 +10,7 @@ import ca.uhn.fhir.jpa.config.SharedConfigDstu3Plus;
import ca.uhn.fhir.jpa.dao.ITransactionProcessorVersionAdapter;
import ca.uhn.fhir.jpa.dao.r5.TransactionProcessorVersionAdapterR5;
import ca.uhn.fhir.jpa.graphql.GraphQLProvider;
import ca.uhn.fhir.jpa.graphql.GraphQLProviderWithIntrospection;
import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl;
import ca.uhn.fhir.jpa.term.TermReadSvcR5;
import ca.uhn.fhir.jpa.term.TermVersionAdapterSvcR5;
@ -17,6 +19,7 @@ import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5;
import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.Meta;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
@ -68,8 +71,8 @@ public class JpaR5Config {
@Bean(name = JpaConfig.GRAPHQL_PROVIDER_NAME)
@Lazy
public GraphQLProvider graphQLProvider(FhirContext theFhirContext, IGraphQLStorageServices theGraphqlStorageServices, IValidationSupport theValidationSupport) {
return new GraphQLProvider(theFhirContext, theValidationSupport, theGraphqlStorageServices);
public GraphQLProvider graphQLProvider(FhirContext theFhirContext, IGraphQLStorageServices theGraphqlStorageServices, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry, IDaoRegistry theDaoRegistry) {
return new GraphQLProviderWithIntrospection(theFhirContext, theValidationSupport, theGraphqlStorageServices, theSearchParamRegistry, theDaoRegistry);
}
@Bean(name = "mySystemDaoR5")

View File

@ -161,6 +161,9 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
@Nullable
@Override
public <T extends IBaseResource> List<T> fetchAllStructureDefinitions() {
if (!myDaoRegistry.isResourceTypeSupported("StructureDefinition")) {
return null;
}
IBundleProvider search = myDaoRegistry.getResourceDao("StructureDefinition").search(new SearchParameterMap().setLoadSynchronousUpTo(1000));
return (List<T>) search.getResources(0, 1000);
}

View File

@ -0,0 +1,269 @@
package ca.uhn.fhir.jpa.graphql;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed 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.
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.IDaoRegistry;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.util.StringUtil;
import ca.uhn.fhir.util.VersionUtil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.scalar.GraphqlStringCoercing;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import org.apache.commons.io.output.StringBuilderWriter;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.common.hapi.validation.validator.VersionSpecificWorkerContextWrapper;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r5.model.Enumerations;
import org.hl7.fhir.r5.model.SearchParameter;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.utils.GraphQLSchemaGenerator;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static ca.uhn.fhir.util.MessageSupplier.msg;
public class GraphQLProviderWithIntrospection extends GraphQLProvider {
private static final Logger ourLog = LoggerFactory.getLogger(GraphQLProviderWithIntrospection.class);
private final GraphQLSchemaGenerator myGenerator;
private final ISearchParamRegistry mySearchParamRegistry;
private final VersionSpecificWorkerContextWrapper myContext;
private final IDaoRegistry myDaoRegistry;
/**
* Constructor
*/
public GraphQLProviderWithIntrospection(FhirContext theFhirContext, IValidationSupport theValidationSupport, IGraphQLStorageServices theIGraphQLStorageServices, ISearchParamRegistry theSearchParamRegistry, IDaoRegistry theDaoRegistry) {
super(theFhirContext, theValidationSupport, theIGraphQLStorageServices);
mySearchParamRegistry = theSearchParamRegistry;
myDaoRegistry = theDaoRegistry;
myContext = VersionSpecificWorkerContextWrapper.newVersionSpecificWorkerContextWrapper(theValidationSupport);
myGenerator = new GraphQLSchemaGenerator(myContext, VersionUtil.getVersion());
}
@Override
public String processGraphQlGetRequest(ServletRequestDetails theRequestDetails, IIdType theId, String theQueryUrl) {
return super.processGraphQlGetRequest(theRequestDetails, theId, theQueryUrl);
}
@Override
public String processGraphQlPostRequest(ServletRequestDetails theServletRequestDetails, RequestDetails theRequestDetails, IIdType theId, String theQueryBody) {
if (theQueryBody.contains("__schema")) {
EnumSet<GraphQLSchemaGenerator.FHIROperationType> operations;
if (theId != null) {
throw new InvalidRequestException(Msg.code(2035) + "GraphQL introspection not supported at instance level. Please try at server- or instance- level.");
}
operations = EnumSet.of(GraphQLSchemaGenerator.FHIROperationType.READ, GraphQLSchemaGenerator.FHIROperationType.SEARCH);
Collection<String> resourceTypes;
if (theRequestDetails.getResourceName() != null) {
resourceTypes = Collections.singleton(theRequestDetails.getResourceName());
} else {
resourceTypes = new HashSet<>();
for (String next : myContext.getResourceNames()) {
if (myDaoRegistry.isResourceTypeSupported(next)) {
resourceTypes.add(next);
}
}
resourceTypes = resourceTypes
.stream()
.sorted()
.collect(Collectors.toList());
}
return generateSchema(theQueryBody, resourceTypes, operations);
} else {
return super.processGraphQlPostRequest(theServletRequestDetails, theRequestDetails, theId, theQueryBody);
}
}
private String generateSchema(String theQueryBody, Collection<String> theResourceTypes, EnumSet<GraphQLSchemaGenerator.FHIROperationType> theOperations) {
final StringBuilder schemaBuilder = new StringBuilder();
try (Writer writer = new StringBuilderWriter(schemaBuilder)) {
// Generate FHIR base types schemas
myGenerator.generateTypes(writer, theOperations);
// Fix up a few things that are missing from the generated schema
writer
.append("\ntype Resource {")
.append("\n id: [token]" + "\n}")
.append("\n");
writer
.append("\ninput ResourceInput {")
.append("\n id: [token]" + "\n}")
.append("\n");
// Generate schemas for the resource types
for (String nextResourceType : theResourceTypes) {
StructureDefinition sd = fetchStructureDefinition(nextResourceType);
List<SearchParameter> parameters = toR5SearchParams(mySearchParamRegistry.getActiveSearchParams(nextResourceType).values());
myGenerator.generateResource(writer, sd, parameters, theOperations);
}
// Generate queries
writer.append("\ntype Query {");
for (String nextResourceType : theResourceTypes) {
if (theOperations.contains(GraphQLSchemaGenerator.FHIROperationType.READ)) {
writer
.append("\n ")
.append(nextResourceType)
.append("(id: String): ")
.append(nextResourceType)
.append("\n");
}
if (theOperations.contains(GraphQLSchemaGenerator.FHIROperationType.SEARCH)) {
List<SearchParameter> parameters = toR5SearchParams(mySearchParamRegistry.getActiveSearchParams(nextResourceType).values());
myGenerator.generateListAccessQuery(writer, parameters, nextResourceType);
myGenerator.generateConnectionAccessQuery(writer, parameters, nextResourceType);
}
}
writer.append("\n}");
writer.flush();
} catch (IOException e) {
throw new InternalErrorException(Msg.code(2036) + e.getMessage(), e);
}
String schema = schemaBuilder.toString().replace("\r", "");
// Set these to INFO if you're testing, then set back before committing
ourLog.debug("Schema generated: {} chars", schema.length());
ourLog.debug("Schema generated: {}", msg(() -> StringUtil.prependLineNumbers(schema)));
SchemaParser schemaParser = new SchemaParser();
TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(schema);
SchemaGenerator schemaGenerator = new SchemaGenerator();
RuntimeWiring.Builder runtimeWiringBuilder = RuntimeWiring.newRuntimeWiring();
for (String next : typeDefinitionRegistry.scalars().keySet()) {
if (Character.isUpperCase(next.charAt(0))) {
// Skip GraphQL built-in types
continue;
}
runtimeWiringBuilder.scalar(new GraphQLScalarType.Builder().name(next).coercing(new GraphqlStringCoercing()).build());
}
RuntimeWiring runtimeWiring = runtimeWiringBuilder.build();
GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
GraphQL build = GraphQL.newGraphQL(graphQLSchema).build();
ExecutionResult executionResult = build.execute(theQueryBody);
Map<String, Object> data = executionResult.toSpecification();
Gson gson = new GsonBuilder().create();
return gson.toJson(data);
}
@Nonnull
private List<SearchParameter> toR5SearchParams(Collection<RuntimeSearchParam> searchParams) {
List<SearchParameter> parameters = new ArrayList<>();
for (RuntimeSearchParam next : searchParams) {
SearchParameter sp = toR5SearchParam(next);
if (sp != null) {
parameters.add(sp);
}
}
return parameters;
}
@Nullable
private SearchParameter toR5SearchParam(RuntimeSearchParam next) {
SearchParameter sp = new SearchParameter();
sp.setUrl(next.getUri());
sp.setCode(next.getName());
sp.setName(next.getName());
switch (next.getParamType()) {
case NUMBER:
sp.setType(Enumerations.SearchParamType.NUMBER);
break;
case DATE:
sp.setType(Enumerations.SearchParamType.DATE);
break;
case STRING:
sp.setType(Enumerations.SearchParamType.STRING);
break;
case TOKEN:
sp.setType(Enumerations.SearchParamType.TOKEN);
break;
case REFERENCE:
sp.setType(Enumerations.SearchParamType.REFERENCE);
break;
case COMPOSITE:
sp.setType(Enumerations.SearchParamType.COMPOSITE);
break;
case QUANTITY:
sp.setType(Enumerations.SearchParamType.QUANTITY);
break;
case URI:
sp.setType(Enumerations.SearchParamType.URI);
break;
case HAS:
case SPECIAL:
default:
return null;
}
return sp;
}
@Nonnull
private StructureDefinition fetchStructureDefinition(String resourceName) {
StructureDefinition retVal = myContext.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName);
Validate.notNull(retVal);
return retVal;
}
}

View File

@ -722,16 +722,6 @@ public abstract class BaseJpaTest extends BaseTest {
doRandomizeLocaleAndTimezone();
}
@AfterAll
public static void afterClassShutdownDerby() {
// DriverManager.getConnection("jdbc:derby:;shutdown=true");
// try {
// DriverManager.getConnection("jdbc:derby:memory:myUnitTestDB;drop=true");
// } catch (SQLNonTransientConnectionException e) {
// // expected.. for some reason....
// }
}
public static String loadClasspath(String resource) throws IOException {
return new String(loadClasspathBytes(resource), Constants.CHARSET_UTF8);
}

View File

@ -17,6 +17,7 @@ import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
@ -105,6 +106,10 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
.collect(Collectors.toList());
assertThat(paths.toString(), paths, contains("Observation.subject", "Observation.subject.where(resolve() is Patient)"));
});
myCaptureQueriesListener.clear();
assertEquals(1, myObservationDao.search(SearchParameterMap.newSynchronous("patient", new ReferenceParam("Patient/A"))).sizeOrThrowNpe());
myCaptureQueriesListener.logSelectQueries();
}
@Test

View File

@ -5,12 +5,9 @@ import ca.uhn.fhir.jpa.graphql.GraphQLProvider;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.io.IOUtils;
@ -19,9 +16,6 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -35,6 +29,7 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@ -47,15 +42,19 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class JpaGraphQLR4ProviderTest {
public class GraphQLR4ProviderTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JpaGraphQLR4ProviderTest.class);
public static final String DATA_PREFIX = "{\"data\": ";
public static final String DATA_SUFFIX = "}";
private static CloseableHttpClient ourClient;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GraphQLR4ProviderTest.class);
private static final FhirContext ourCtx = FhirContext.forR4Cached();
private static int ourPort;
private static Server ourServer;
private static CloseableHttpClient ourClient;
private MyStorageServices myGraphQLStorageServices = new MyStorageServices();
@RegisterExtension
private RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(ourCtx)
.registerProvider(new DummyPatientResourceProvider())
.registerProvider(new GraphQLProvider(myGraphQLStorageServices));
@BeforeEach
public void before() {
@ -65,9 +64,8 @@ public class JpaGraphQLR4ProviderTest {
@Test
public void testGraphInstance() throws Exception {
String query = "{name{family,given}}";
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$graphql?query=" + UrlUtil.escapeUrlParam(query));
CloseableHttpResponse status = ourClient.execute(httpGet);
try {
HttpGet httpGet = new HttpGet("http://localhost:" + myRestfulServerExtension.getPort() + "/Patient/123/$graphql?query=" + UrlUtil.escapeUrlParam(query));
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
@ -81,9 +79,6 @@ public class JpaGraphQLR4ProviderTest {
" }]\n" +
"}" + DATA_SUFFIX), TestUtil.stripWhitespace(responseContent));
assertThat(status.getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue(), startsWith("application/json"));
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
}
@ -91,9 +86,8 @@ public class JpaGraphQLR4ProviderTest {
@Test
public void testGraphInstanceWithFhirpath() throws Exception {
String query = "{name(fhirpath:\"family.exists()\"){text,given,family}}";
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$graphql?query=" + UrlUtil.escapeUrlParam(query));
CloseableHttpResponse status = ourClient.execute(httpGet);
try {
HttpGet httpGet = new HttpGet("http://localhost:" + myRestfulServerExtension.getPort() + "/Patient/123/$graphql?query=" + UrlUtil.escapeUrlParam(query));
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
@ -105,9 +99,6 @@ public class JpaGraphQLR4ProviderTest {
" }]\n" +
"}" + DATA_SUFFIX), TestUtil.stripWhitespace(responseContent));
assertThat(status.getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue(), startsWith("application/json"));
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
}
@ -115,9 +106,8 @@ public class JpaGraphQLR4ProviderTest {
@Test
public void testGraphSystemInstance() throws Exception {
String query = "{Patient(id:123){id,name{given,family}}}";
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$graphql?query=" + UrlUtil.escapeUrlParam(query));
CloseableHttpResponse status = ourClient.execute(httpGet);
try {
HttpGet httpGet = new HttpGet("http://localhost:" + myRestfulServerExtension.getPort() + "/$graphql?query=" + UrlUtil.escapeUrlParam(query));
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
@ -133,9 +123,6 @@ public class JpaGraphQLR4ProviderTest {
" }\n" +
"}" + DATA_SUFFIX), TestUtil.stripWhitespace(responseContent));
assertThat(status.getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue(), startsWith("application/json"));
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
}
@ -143,9 +130,9 @@ public class JpaGraphQLR4ProviderTest {
@Test
public void testGraphSystemList() throws Exception {
String query = "{PatientList(name:\"pet\"){name{family,given}}}";
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$graphql?query=" + UrlUtil.escapeUrlParam(query));
CloseableHttpResponse status = ourClient.execute(httpGet);
try {
HttpGet httpGet = new HttpGet("http://localhost:" + myRestfulServerExtension.getPort() + "/$graphql?query=" + UrlUtil.escapeUrlParam(query));
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
@ -166,8 +153,6 @@ public class JpaGraphQLR4ProviderTest {
"}" + DATA_SUFFIX), TestUtil.stripWhitespace(responseContent));
assertThat(status.getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue(), startsWith("application/json"));
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
}
@ -175,9 +160,8 @@ public class JpaGraphQLR4ProviderTest {
@Test
public void testGraphSystemArrayArgumentList() throws Exception {
String query = "{PatientList(id:[\"hapi-123\",\"hapi-124\"]){id,name{family}}}";
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$graphql?query=" + UrlUtil.escapeUrlParam(query));
CloseableHttpResponse status = ourClient.execute(httpGet);
try {
HttpGet httpGet = new HttpGet("http://localhost:" + myRestfulServerExtension.getPort() + "/$graphql?query=" + UrlUtil.escapeUrlParam(query));
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
@ -196,41 +180,21 @@ public class JpaGraphQLR4ProviderTest {
" }]\n" +
"}" + DATA_SUFFIX), TestUtil.stripWhitespace(responseContent));
assertThat(status.getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue(), startsWith("application/json"));
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
}
@AfterAll
public static void afterClassClearContext() throws Exception {
JettyUtil.closeServer(ourServer);
ourClient.close();
}
@BeforeAll
public static void beforeClass() throws Exception {
ourServer = new Server(0);
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.setDefaultResponseEncoding(EncodingEnum.JSON);
servlet.setPagingProvider(new FifoMemoryPagingProvider(10));
servlet.registerProvider(new DummyPatientResourceProvider());
MyStorageServices storageServices = new MyStorageServices();
servlet.registerProvider(new GraphQLProvider(storageServices));
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
JettyUtil.startServer(ourServer);
ourPort = JettyUtil.getPortForStartedServer(ourServer);
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
public static class DummyPatientResourceProvider implements IResourceProvider {

View File

@ -14,8 +14,8 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static ca.uhn.fhir.jpa.provider.JpaGraphQLR4ProviderTest.DATA_PREFIX;
import static ca.uhn.fhir.jpa.provider.JpaGraphQLR4ProviderTest.DATA_SUFFIX;
import static ca.uhn.fhir.jpa.provider.GraphQLR4ProviderTest.DATA_PREFIX;
import static ca.uhn.fhir.jpa.provider.GraphQLR4ProviderTest.DATA_SUFFIX;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class GraphQLProviderDstu3Test extends BaseResourceProviderDstu3Test {

View File

@ -1,93 +0,0 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static ca.uhn.fhir.jpa.provider.JpaGraphQLR4ProviderTest.DATA_PREFIX;
import static ca.uhn.fhir.jpa.provider.JpaGraphQLR4ProviderTest.DATA_SUFFIX;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class GraphQLProviderR4Test extends BaseResourceProviderR4Test {
private Logger ourLog = LoggerFactory.getLogger(GraphQLProviderR4Test.class);
private IIdType myPatientId0;
@Test
public void testInstanceSimpleRead() throws IOException {
initTestPatients();
String query = "{name{family,given}}";
HttpGet httpGet = new HttpGet(ourServerBase + "/Patient/" + myPatientId0.getIdPart() + "/$graphql?query=" + UrlUtil.escapeUrlParam(query));
try (CloseableHttpResponse response = ourHttpClient.execute(httpGet)) {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(resp);
assertEquals(TestUtil.stripWhitespace(DATA_PREFIX + "{\n" +
" \"name\":[{\n" +
" \"family\":\"FAM\",\n" +
" \"given\":[\"GIVEN1\",\"GIVEN2\"]\n" +
" },{\n" +
" \"given\":[\"GivenOnly1\",\"GivenOnly2\"]\n" +
" }]\n" +
"}" + DATA_SUFFIX), TestUtil.stripWhitespace(resp));
}
}
@Test
public void testSystemSimpleSearch() throws IOException {
initTestPatients();
String query = "{PatientList(given:\"given\"){name{family,given}}}";
HttpGet httpGet = new HttpGet(ourServerBase + "/$graphql?query=" + UrlUtil.escapeUrlParam(query));
try (CloseableHttpResponse response = ourHttpClient.execute(httpGet)) {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(resp);
assertEquals(TestUtil.stripWhitespace(DATA_PREFIX + "{\n" +
" \"PatientList\":[{\n" +
" \"name\":[{\n" +
" \"family\":\"FAM\",\n" +
" \"given\":[\"GIVEN1\",\"GIVEN2\"]\n" +
" },{\n" +
" \"given\":[\"GivenOnly1\",\"GivenOnly2\"]\n" +
" }]\n" +
" },{\n" +
" \"name\":[{\n" +
" \"given\":[\"GivenOnlyB1\",\"GivenOnlyB2\"]\n" +
" }]\n" +
" }]\n" +
"}" + DATA_SUFFIX), TestUtil.stripWhitespace(resp));
}
}
private void initTestPatients() {
Patient p = new Patient();
p.addName()
.setFamily("FAM")
.addGiven("GIVEN1")
.addGiven("GIVEN2");
p.addName()
.addGiven("GivenOnly1")
.addGiven("GivenOnly2");
myPatientId0 = myClient.create().resource(p).execute().getId().toUnqualifiedVersionless();
p = new Patient();
p.addName()
.addGiven("GivenOnlyB1")
.addGiven("GivenOnlyB2");
myClient.create().resource(p).execute();
}
}

View File

@ -0,0 +1,227 @@
package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.util.FileUtil;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static ca.uhn.fhir.jpa.provider.GraphQLR4ProviderTest.DATA_PREFIX;
import static ca.uhn.fhir.jpa.provider.GraphQLR4ProviderTest.DATA_SUFFIX;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals;
@TestMethodOrder(MethodOrderer.MethodName.class)
public class GraphQLR4Test extends BaseResourceProviderR4Test {
public static final String INTROSPECTION_QUERY = "{\"query\":\"\\n query IntrospectionQuery {\\n __schema {\\n queryType { name }\\n mutationType { name }\\n subscriptionType { name }\\n types {\\n ...FullType\\n }\\n directives {\\n name\\n description\\n locations\\n args {\\n ...InputValue\\n }\\n }\\n }\\n }\\n\\n fragment FullType on __Type {\\n kind\\n name\\n description\\n fields(includeDeprecated: true) {\\n name\\n description\\n args {\\n ...InputValue\\n }\\n type {\\n ...TypeRef\\n }\\n isDeprecated\\n deprecationReason\\n }\\n inputFields {\\n ...InputValue\\n }\\n interfaces {\\n ...TypeRef\\n }\\n enumValues(includeDeprecated: true) {\\n name\\n description\\n isDeprecated\\n deprecationReason\\n }\\n possibleTypes {\\n ...TypeRef\\n }\\n }\\n\\n fragment InputValue on __InputValue {\\n name\\n description\\n type { ...TypeRef }\\n defaultValue\\n }\\n\\n fragment TypeRef on __Type {\\n kind\\n name\\n ofType {\\n kind\\n name\\n ofType {\\n kind\\n name\\n ofType {\\n kind\\n name\\n ofType {\\n kind\\n name\\n ofType {\\n kind\\n name\\n ofType {\\n kind\\n name\\n ofType {\\n kind\\n name\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n \",\"operationName\":\"IntrospectionQuery\"}";
private Logger ourLog = LoggerFactory.getLogger(GraphQLR4Test.class);
private IIdType myPatientId0;
@Test
public void testInstance_Read_Patient() throws IOException {
initTestPatients();
String query = "{name{family,given}}";
HttpGet httpGet = new HttpGet(ourServerBase + "/Patient/" + myPatientId0.getIdPart() + "/$graphql?query=" + UrlUtil.escapeUrlParam(query));
try (CloseableHttpResponse response = ourHttpClient.execute(httpGet)) {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(resp);
assertEquals(TestUtil.stripWhitespace(DATA_PREFIX + "{\n" +
" \"name\":[{\n" +
" \"family\":\"FAM\",\n" +
" \"given\":[\"GIVEN1\",\"GIVEN2\"]\n" +
" },{\n" +
" \"given\":[\"GivenOnly1\",\"GivenOnly2\"]\n" +
" }]\n" +
"}" + DATA_SUFFIX), TestUtil.stripWhitespace(resp));
}
}
@Test
public void testType_Introspect_Patient() throws IOException {
initTestPatients();
String uri = ourServerBase + "/Patient/$graphql";
HttpPost httpGet = new HttpPost(uri);
httpGet.setEntity(new StringEntity(INTROSPECTION_QUERY, ContentType.APPLICATION_JSON));
// Repeat a couple of times to make sure it doesn't fail after the first one. At one point
// the generator polluted the structure userdata and failed the second time
for (int i = 0; i < 3; i++) {
try (CloseableHttpResponse response = ourHttpClient.execute(httpGet)) {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(resp);
assertEquals(200, response.getStatusLine().getStatusCode());
assertThat(resp, containsString("{\"kind\":\"OBJECT\",\"name\":\"Patient\","));
assertThat(resp, not(containsString("{\"kind\":\"OBJECT\",\"name\":\"Observation\",")));
assertThat(resp, not(containsString("\"name\":\"Observation\",\"args\":[{\"name\":\"id\"")));
assertThat(resp, not(containsString("\"name\":\"ObservationList\",\"args\":[{\"name\":\"_filter\"")));
assertThat(resp, not(containsString("\"name\":\"ObservationConnection\",\"fields\":[{\"name\":\"count\"")));
assertThat(resp, containsString("\"name\":\"Patient\",\"args\":[{\"name\":\"id\""));
assertThat(resp, containsString("\"name\":\"PatientList\",\"args\":[{\"name\":\"_filter\""));
}
}
}
@Test
public void testType_Introspect_Observation() throws IOException {
initTestPatients();
String uri = ourServerBase + "/Observation/$graphql";
HttpPost httpGet = new HttpPost(uri);
httpGet.setEntity(new StringEntity(INTROSPECTION_QUERY, ContentType.APPLICATION_JSON));
// Repeat a couple of times to make sure it doesn't fail after the first one. At one point
// the generator polluted the structure userdata and failed the second time
for (int i = 0; i < 3; i++) {
try (CloseableHttpResponse response = ourHttpClient.execute(httpGet)) {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(resp);
assertEquals(200, response.getStatusLine().getStatusCode());
assertThat(resp, not(containsString("{\"kind\":\"OBJECT\",\"name\":\"Patient\",")));
assertThat(resp, containsString("{\"kind\":\"OBJECT\",\"name\":\"Observation\","));
assertThat(resp, not(containsString("{\"kind\":\"OBJECT\",\"name\":\"Query\",\"fields\":[{\"name\":\"PatientList\"")));
assertThat(resp, containsString("\"name\":\"Observation\",\"args\":[{\"name\":\"id\""));
assertThat(resp, containsString("\"name\":\"ObservationList\",\"args\":[{\"name\":\"_filter\""));
assertThat(resp, containsString("\"name\":\"ObservationConnection\",\"fields\":[{\"name\":\"count\""));
assertThat(resp, not(containsString("\"name\":\"Patient\",\"args\":[{\"name\":\"id\"")));
assertThat(resp, not(containsString("\"name\":\"PatientList\",\"args\":[{\"name\":\"_filter\"")));
}
}
}
@Test
public void testRoot_Introspect() throws IOException {
initTestPatients();
String uri = ourServerBase + "/$graphql";
HttpPost httpGet = new HttpPost(uri);
httpGet.setEntity(new StringEntity(INTROSPECTION_QUERY, ContentType.APPLICATION_JSON));
// Repeat a couple of times to make sure it doesn't fail after the first one. At one point
// the generator polluted the structure userdata and failed the second time
for (int i = 0; i < 3; i++) {
try (CloseableHttpResponse response = ourHttpClient.execute(httpGet)) {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info("Response has size: {}", FileUtil.formatFileSize(resp.length()));
assertEquals(200, response.getStatusLine().getStatusCode());
assertThat(resp, containsString("{\"kind\":\"OBJECT\",\"name\":\"Patient\","));
assertThat(resp, containsString("{\"kind\":\"OBJECT\",\"name\":\"Observation\","));
assertThat(resp, containsString("\"name\":\"Observation\",\"args\":[{\"name\":\"id\""));
assertThat(resp, containsString("\"name\":\"ObservationList\",\"args\":[{\"name\":\"_filter\""));
assertThat(resp, containsString("\"name\":\"ObservationConnection\",\"fields\":[{\"name\":\"count\""));
assertThat(resp, containsString("\"name\":\"Patient\",\"args\":[{\"name\":\"id\""));
assertThat(resp, containsString("\"name\":\"PatientList\",\"args\":[{\"name\":\"_filter\""));
}
}
}
@Test
public void testRoot_Read_Patient() throws IOException {
initTestPatients();
String query = "{Patient(id:\"" + myPatientId0.getIdPart() + "\"){name{family,given}}}";
HttpGet httpGet = new HttpGet(ourServerBase + "/$graphql?query=" + UrlUtil.escapeUrlParam(query));
try (CloseableHttpResponse response = ourHttpClient.execute(httpGet)) {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(resp);
assertEquals(TestUtil.stripWhitespace(DATA_PREFIX +
"{\n" +
"\"Patient\":{\n" +
"\"name\":[{\n" +
"\"family\":\"FAM\",\n" +
"\"given\":[\"GIVEN1\",\"GIVEN2\"]\n" +
"},{\n" +
"\"given\":[\"GivenOnly1\",\"GivenOnly2\"]\n" +
"}]\n" +
"}\n" +
"}" +
DATA_SUFFIX), TestUtil.stripWhitespace(resp));
}
}
@Test
public void testRoot_Search_Patient() throws IOException {
initTestPatients();
String query = "{PatientList(given:\"given\"){name{family,given}}}";
HttpGet httpGet = new HttpGet(ourServerBase + "/$graphql?query=" + UrlUtil.escapeUrlParam(query));
try (CloseableHttpResponse response = ourHttpClient.execute(httpGet)) {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(resp);
assertEquals(TestUtil.stripWhitespace(DATA_PREFIX + "{\n" +
" \"PatientList\":[{\n" +
" \"name\":[{\n" +
" \"family\":\"FAM\",\n" +
" \"given\":[\"GIVEN1\",\"GIVEN2\"]\n" +
" },{\n" +
" \"given\":[\"GivenOnly1\",\"GivenOnly2\"]\n" +
" }]\n" +
" },{\n" +
" \"name\":[{\n" +
" \"given\":[\"GivenOnlyB1\",\"GivenOnlyB2\"]\n" +
" }]\n" +
" }]\n" +
"}" + DATA_SUFFIX), TestUtil.stripWhitespace(resp));
}
}
@Test
public void testRoot_Search_Observation() throws IOException {
initTestPatients();
String query = "{ObservationList(date: \"2022\") {id}}";
HttpGet httpGet = new HttpGet(ourServerBase + "/$graphql?query=" + UrlUtil.escapeUrlParam(query));
myCaptureQueriesListener.clear();
try (CloseableHttpResponse response = ourHttpClient.execute(httpGet)) {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(resp);
}
myCaptureQueriesListener.logSelectQueries();
}
private void initTestPatients() {
Patient p = new Patient();
p.addName()
.setFamily("FAM")
.addGiven("GIVEN1")
.addGiven("GIVEN2");
p.addName()
.addGiven("GivenOnly1")
.addGiven("GivenOnly2");
myPatientId0 = myClient.create().resource(p).execute().getId().toUnqualifiedVersionless();
p = new Patient();
p.addName()
.addGiven("GivenOnlyB1")
.addGiven("GivenOnlyB2");
myClient.create().resource(p).execute();
}
}

View File

@ -19,6 +19,7 @@
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-base</artifactId>
@ -102,6 +103,12 @@
</dependency>
<!-- TEST DEPS -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>

View File

@ -35,6 +35,7 @@ import ca.uhn.fhir.rest.server.interceptor.FhirPathFilterInterceptor;
import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory;
import ca.uhn.fhirtest.config.SqlCaptureInterceptor;
import ca.uhn.fhirtest.config.TestDstu2Config;
import ca.uhn.fhirtest.config.TestDstu3Config;
import ca.uhn.fhirtest.config.TestR4Config;
@ -282,6 +283,8 @@ public class TestRestfulServer extends RestfulServer {
loggingInterceptor.setMessageFormat("${operationType} Content-Type: ${requestHeader.content-type} - Accept: ${responseEncodingNoDefault} \"${requestHeader.accept}\" - Agent: ${requestHeader.user-agent}");
registerInterceptor(loggingInterceptor);
// SQL Capturing
registerInterceptor(myAppCtx.getBean(SqlCaptureInterceptor.class));
}
/**

View File

@ -5,11 +5,11 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.config.HapiJpaConfig;
import ca.uhn.fhir.jpa.config.JpaDstu2Config;
import ca.uhn.fhir.jpa.config.util.HapiEntityManagerFactoryUtil;
import ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect;
import ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.search.HapiLuceneAnalysisConfigurer;
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
import ca.uhn.fhir.jpa.validation.ValidationSettings;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.IValidatorModule;
@ -22,7 +22,6 @@ import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings;
import org.hibernate.search.engine.cfg.BackendSettings;
import org.hl7.fhir.dstu2.model.Subscription;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -44,16 +43,11 @@ import java.util.concurrent.TimeUnit;
@EnableTransactionManagement()
public class TestDstu2Config {
public static final String FHIR_LUCENE_LOCATION_DSTU2 = "${fhir.lucene.location.dstu2}";
public static final String FHIR_LUCENE_LOCATION_DSTU2 = "fhir.lucene.location.dstu2";
@Value(TestDstu3Config.FHIR_DB_USERNAME)
private String myDbUsername;
@Value(TestDstu3Config.FHIR_DB_PASSWORD)
private String myDbPassword;
@Value(FHIR_LUCENE_LOCATION_DSTU2)
private String myFhirLuceneLocation;
private String myDbUsername = System.getProperty(TestR5Config.FHIR_DB_USERNAME);
private String myDbPassword = System.getProperty(TestR5Config.FHIR_DB_PASSWORD);
private String myFhirLuceneLocation = System.getProperty(FHIR_LUCENE_LOCATION_DSTU2);
@Bean
public PublicSecurityInterceptor securityInterceptor() {
@ -88,7 +82,9 @@ public class TestDstu2Config {
@Bean
public ModelConfig modelConfig() {
return daoConfig().getModelConfig();
ModelConfig retVal = daoConfig().getModelConfig();
retVal.setIndexIdentifierOfType(true);
return retVal;
}
@Bean
@ -103,7 +99,7 @@ public class TestDstu2Config {
public DataSource dataSource() {
BasicDataSource retVal = new BasicDataSource();
if (CommonConfig.isLocalTestMode()) {
retVal.setUrl("jdbc:derby:memory:fhirtest_dstu2;create=true");
retVal.setUrl("jdbc:h2:mem:fhirtest_dstu2");
} else {
retVal.setDriver(new org.postgresql.Driver());
retVal.setUrl("jdbc:postgresql://localhost/fhirtest_dstu2");
@ -144,7 +140,7 @@ public class TestDstu2Config {
private Properties jpaProperties() {
Properties extraProperties = new Properties();
if (CommonConfig.isLocalTestMode()) {
extraProperties.put("hibernate.dialect", DerbyTenSevenHapiFhirDialect.class.getName());
extraProperties.put("hibernate.dialect", HapiFhirH2Dialect.class.getName());
} else {
extraProperties.put("hibernate.dialect", HapiFhirPostgres94Dialect.class.getName());
}
@ -168,6 +164,7 @@ public class TestDstu2Config {
/**
* Bean which validates incoming requests
*
* @param theInstanceValidator
*/
@Bean

View File

@ -5,12 +5,12 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.config.HapiJpaConfig;
import ca.uhn.fhir.jpa.config.dstu3.JpaDstu3Config;
import ca.uhn.fhir.jpa.config.util.HapiEntityManagerFactoryUtil;
import ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect;
import ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.HapiLuceneAnalysisConfigurer;
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
import ca.uhn.fhir.jpa.validation.ValidationSettings;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.IInstanceValidatorModule;
@ -23,7 +23,6 @@ import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings;
import org.hibernate.search.engine.cfg.BackendSettings;
import org.hl7.fhir.dstu2.model.Subscription;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -48,14 +47,9 @@ public class TestDstu3Config {
public static final String FHIR_DB_PASSWORD = "${fhir.db.password}";
public static final String FHIR_LUCENE_LOCATION_DSTU3 = "${fhir.lucene.location.dstu3}";
@Value(TestDstu3Config.FHIR_DB_USERNAME)
private String myDbUsername;
@Value(TestDstu3Config.FHIR_DB_PASSWORD)
private String myDbPassword;
@Value(FHIR_LUCENE_LOCATION_DSTU3)
private String myFhirLuceneLocation;
private String myDbUsername = System.getProperty(TestR5Config.FHIR_DB_USERNAME);
private String myDbPassword = System.getProperty(TestR5Config.FHIR_DB_PASSWORD);
private String myFhirLuceneLocation = System.getProperty(FHIR_LUCENE_LOCATION_DSTU3);
@Bean
public DaoConfig daoConfig() {
@ -85,7 +79,9 @@ public class TestDstu3Config {
@Bean
public ModelConfig modelConfig() {
return daoConfig().getModelConfig();
ModelConfig retVal = daoConfig().getModelConfig();
retVal.setIndexIdentifierOfType(true);
return retVal;
}
@ -114,7 +110,7 @@ public class TestDstu3Config {
public DataSource dataSource() {
BasicDataSource retVal = new BasicDataSource();
if (CommonConfig.isLocalTestMode()) {
retVal.setUrl("jdbc:derby:memory:fhirtest_dstu3;create=true");
retVal.setUrl("jdbc:h2:mem:fhirtest_dstu3");
} else {
retVal.setDriver(new org.postgresql.Driver());
retVal.setUrl("jdbc:postgresql://localhost/fhirtest_dstu3");
@ -147,7 +143,7 @@ public class TestDstu3Config {
private Properties jpaProperties() {
Properties extraProperties = new Properties();
if (CommonConfig.isLocalTestMode()) {
extraProperties.put("hibernate.dialect", DerbyTenSevenHapiFhirDialect.class.getName());
extraProperties.put("hibernate.dialect", HapiFhirH2Dialect.class.getName());
} else {
extraProperties.put("hibernate.dialect", HapiFhirPostgres94Dialect.class.getName());
}
@ -171,6 +167,7 @@ public class TestDstu3Config {
/**
* Bean which validates incoming requests
*
* @param theFhirInstanceValidator
*/
@Bean

View File

@ -5,12 +5,12 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.config.HapiJpaConfig;
import ca.uhn.fhir.jpa.config.r4.JpaR4Config;
import ca.uhn.fhir.jpa.config.util.HapiEntityManagerFactoryUtil;
import ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect;
import ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.HapiLuceneAnalysisConfigurer;
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
import ca.uhn.fhir.jpa.validation.ValidationSettings;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.IInstanceValidatorModule;
@ -23,7 +23,6 @@ import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings;
import org.hibernate.search.engine.cfg.BackendSettings;
import org.hl7.fhir.dstu2.model.Subscription;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -49,14 +48,9 @@ public class TestR4Config {
public static final String FHIR_LUCENE_LOCATION_R4 = "${fhir.lucene.location.r4}";
public static final Integer COUNT_SEARCH_RESULTS_UP_TO = 50000;
@Value(TestR4Config.FHIR_DB_USERNAME)
private String myDbUsername;
@Value(TestR4Config.FHIR_DB_PASSWORD)
private String myDbPassword;
@Value(FHIR_LUCENE_LOCATION_R4)
private String myFhirLuceneLocation;
private String myDbUsername = System.getProperty(TestR5Config.FHIR_DB_USERNAME);
private String myDbPassword = System.getProperty(TestR5Config.FHIR_DB_PASSWORD);
private String myFhirLuceneLocation = System.getProperty(FHIR_LUCENE_LOCATION_R4);
@Bean
public DaoConfig daoConfig() {
@ -85,7 +79,9 @@ public class TestR4Config {
@Bean
public ModelConfig modelConfig() {
return daoConfig().getModelConfig();
ModelConfig retVal = daoConfig().getModelConfig();
retVal.setIndexIdentifierOfType(true);
return retVal;
}
@ -101,7 +97,7 @@ public class TestR4Config {
public DataSource dataSource() {
BasicDataSource retVal = new BasicDataSource();
if (CommonConfig.isLocalTestMode()) {
retVal.setUrl("jdbc:derby:memory:fhirtest_r4;create=true");
retVal.setUrl("jdbc:h2:mem:fhirtest_r4");
} else {
retVal.setDriver(new org.postgresql.Driver());
retVal.setUrl("jdbc:postgresql://localhost/fhirtest_r4");
@ -142,7 +138,7 @@ public class TestR4Config {
private Properties jpaProperties() {
Properties extraProperties = new Properties();
if (CommonConfig.isLocalTestMode()) {
extraProperties.put("hibernate.dialect", DerbyTenSevenHapiFhirDialect.class.getName());
extraProperties.put("hibernate.dialect", HapiFhirH2Dialect.class.getName());
} else {
extraProperties.put("hibernate.dialect", HapiFhirPostgres94Dialect.class.getName());
}
@ -165,6 +161,7 @@ public class TestR4Config {
/**
* Bean which validates incoming requests
*
* @param theFhirInstanceValidator
*/
@Bean
@ -202,5 +199,4 @@ public class TestR4Config {
}
}

View File

@ -5,12 +5,12 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.config.HapiJpaConfig;
import ca.uhn.fhir.jpa.config.r5.JpaR5Config;
import ca.uhn.fhir.jpa.config.util.HapiEntityManagerFactoryUtil;
import ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect;
import ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.HapiLuceneAnalysisConfigurer;
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
import ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect;
import ca.uhn.fhir.jpa.validation.ValidationSettings;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.IInstanceValidatorModule;
@ -23,7 +23,8 @@ import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings;
import org.hibernate.search.engine.cfg.BackendSettings;
import org.hl7.fhir.dstu2.model.Subscription;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
import org.springframework.beans.factory.annotation.Value;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -48,15 +49,10 @@ public class TestR5Config {
public static final String FHIR_DB_PASSWORD = "${fhir.db.password}";
public static final String FHIR_LUCENE_LOCATION_R5 = "${fhir.lucene.location.r5}";
public static final Integer COUNT_SEARCH_RESULTS_UP_TO = 50000;
@Value(TestR5Config.FHIR_DB_USERNAME)
private String myDbUsername;
@Value(TestR5Config.FHIR_DB_PASSWORD)
private String myDbPassword;
@Value(FHIR_LUCENE_LOCATION_R5)
private String myFhirLuceneLocation;
private static final Logger ourLog = LoggerFactory.getLogger(TestR5Config.class);
private String myDbUsername = System.getProperty(TestR5Config.FHIR_DB_USERNAME);
private String myDbPassword = System.getProperty(TestR5Config.FHIR_DB_PASSWORD);
private String myFhirLuceneLocation = System.getProperty(FHIR_LUCENE_LOCATION_R5);
@Bean
public DaoConfig daoConfig() {
@ -85,7 +81,9 @@ public class TestR5Config {
@Bean
public ModelConfig modelConfig() {
return daoConfig().getModelConfig();
ModelConfig retVal = daoConfig().getModelConfig();
retVal.setIndexIdentifierOfType(true);
return retVal;
}
@Bean
@ -97,9 +95,12 @@ public class TestR5Config {
@Bean(name = "myPersistenceDataSourceR5")
public DataSource dataSource() {
ourLog.info("Starting R5 database with DB username: {}", myDbUsername);
ourLog.info("Have system property username: {}", System.getProperty(FHIR_DB_USERNAME));
BasicDataSource retVal = new BasicDataSource();
if (CommonConfig.isLocalTestMode()) {
retVal.setUrl("jdbc:derby:memory:fhirtest_r5;create=true");
retVal.setUrl("jdbc:h2:mem:fhirtest_r5");
} else {
retVal.setDriver(new org.postgresql.Driver());
retVal.setUrl("jdbc:postgresql://localhost/fhirtest_r5");
@ -141,7 +142,7 @@ public class TestR5Config {
private Properties jpaProperties() {
Properties extraProperties = new Properties();
if (CommonConfig.isLocalTestMode()) {
extraProperties.put("hibernate.dialect", DerbyTenSevenHapiFhirDialect.class.getName());
extraProperties.put("hibernate.dialect", HapiFhirH2Dialect.class.getName());
} else {
extraProperties.put("hibernate.dialect", HapiFhirPostgres94Dialect.class.getName());
}
@ -165,6 +166,7 @@ public class TestR5Config {
/**
* Bean which validates incoming requests
*
* @param theFhirInstanceValidator
*/
@Bean
@ -202,5 +204,4 @@ public class TestR5Config {
}
}

View File

@ -24,6 +24,7 @@ import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.rest.annotation.GraphQL;
@ -32,6 +33,7 @@ import ca.uhn.fhir.rest.annotation.GraphQLQueryUrl;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Initialize;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -39,12 +41,12 @@ import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.Validate;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.utilities.graphql.IGraphQLEngine;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.hl7.fhir.utilities.graphql.ObjectValue;
import org.hl7.fhir.utilities.graphql.Package;
import org.hl7.fhir.utilities.graphql.Parser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -54,9 +56,10 @@ import javax.annotation.Nullable;
import java.util.function.Supplier;
public class GraphQLProvider {
private final Supplier<IGraphQLEngine> engineFactory;
private static final Logger ourLog = LoggerFactory.getLogger(GraphQLProvider.class);
private final Supplier<IGraphQLEngine> myEngineFactory;
private final IGraphQLStorageServices myStorageServices;
private Logger ourLog = LoggerFactory.getLogger(GraphQLProvider.class);
/**
* Constructor which uses a default context and validation support object
@ -83,21 +86,21 @@ public class GraphQLProvider {
IValidationSupport validationSupport = theValidationSupport;
validationSupport = ObjectUtils.defaultIfNull(validationSupport, new DefaultProfileValidationSupport(theFhirContext));
org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext workerContext = new org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext(theFhirContext, validationSupport);
engineFactory = () -> new org.hl7.fhir.dstu3.utils.GraphQLEngine(workerContext);
myEngineFactory = () -> new org.hl7.fhir.dstu3.utils.GraphQLEngine(workerContext);
break;
}
case R4: {
IValidationSupport validationSupport = theValidationSupport;
validationSupport = ObjectUtils.defaultIfNull(validationSupport, new DefaultProfileValidationSupport(theFhirContext));
org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext workerContext = new org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext(theFhirContext, validationSupport);
engineFactory = () -> new org.hl7.fhir.r4.utils.GraphQLEngine(workerContext);
myEngineFactory = () -> new org.hl7.fhir.r4.utils.GraphQLEngine(workerContext);
break;
}
case R5: {
IValidationSupport validationSupport = theValidationSupport;
validationSupport = ObjectUtils.defaultIfNull(validationSupport, new DefaultProfileValidationSupport(theFhirContext));
org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext workerContext = new org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext(theFhirContext, validationSupport);
engineFactory = () -> new org.hl7.fhir.r5.utils.GraphQLEngine(workerContext);
myEngineFactory = () -> new org.hl7.fhir.r5.utils.GraphQLEngine(workerContext);
break;
}
case DSTU2:
@ -122,22 +125,29 @@ public class GraphQLProvider {
@Description(value = "This operation invokes a GraphQL expression for fetching an joining a graph of resources, returning them in a custom format.")
@GraphQL(type = RequestTypeEnum.POST)
public String processGraphQlPostRequest(ServletRequestDetails theRequestDetails, @IdParam IIdType theId, @GraphQLQueryBody String theQueryBody) {
public String processGraphQlPostRequest(ServletRequestDetails theServletRequestDetails, RequestDetails theRequestDetails, @IdParam IIdType theId, @GraphQLQueryBody String theQueryBody) {
if (theQueryBody != null) {
return processGraphQLRequest(theRequestDetails, theId, theQueryBody);
return processGraphQLRequest(theServletRequestDetails, theId, theQueryBody);
}
throw new InvalidRequestException(Msg.code(1145) + "Unable to parse empty GraphQL expression");
}
public String processGraphQLRequest(ServletRequestDetails theRequestDetails, IIdType theId, String theQuery) {
IGraphQLEngine engine = engineFactory.get();
Package parsedGraphQLRequest;
try {
parsedGraphQLRequest = Parser.parse(theQuery);
} catch (Exception e) {
throw new InvalidRequestException(Msg.code(1146) + "Unable to parse GraphQL Expression: " + e);
}
return processGraphQLRequest(theRequestDetails, theId, parsedGraphQLRequest);
}
protected String processGraphQLRequest(ServletRequestDetails theRequestDetails, IIdType theId, Package parsedGraphQLRequest) {
IGraphQLEngine engine = myEngineFactory.get();
engine.setAppInfo(theRequestDetails);
engine.setServices(myStorageServices);
try {
engine.setGraphQL(Parser.parse(theQuery));
} catch (Exception theE) {
throw new InvalidRequestException(Msg.code(1146) + "Unable to parse GraphQL Expression: " + theE.toString());
}
engine.setGraphQL(parsedGraphQLRequest);
try {
@ -167,7 +177,7 @@ public class GraphQLProvider {
ourLog.error("Failure during GraphQL processing", e);
}
b.append(e.getMessage());
throw new UnclassifiedServerFailureException(statusCode, Msg.code(1147) + b.toString());
throw new UnclassifiedServerFailureException(statusCode, Msg.code(1147) + b);
}
}

View File

@ -41,7 +41,7 @@ import static org.mockito.Mockito.when;
public class LoggingInterceptorTest {
private static FhirContext ourCtx = FhirContext.forR4();
private static final FhirContext ourCtx = FhirContext.forR4Cached();
private static int ourPort;
private static Server ourServer;
private Logger myLoggerRoot;

View File

@ -8,10 +8,10 @@ import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.io.IOUtils;
@ -22,9 +22,6 @@ import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.IdType;
@ -33,10 +30,10 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -44,63 +41,37 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
public class GraphQLR4RawTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GraphQLR4RawTest.class);
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx = FhirContext.forR4();
private static int ourPort;
private static Server ourServer;
private static final FhirContext ourCtx = FhirContext.forR4Cached();
private static String ourNextRetVal;
private static IdType ourLastId;
private static String ourLastQuery;
private static int ourMethodCount;
private static String ourLastResourceType;
@AfterAll
public static void afterClassClearContext() throws Exception {
JettyUtil.closeServer(ourServer);
TestUtil.randomizeLocaleAndTimezone();
}
@BeforeAll
public static void beforeClass() throws Exception {
ourServer = new Server(0);
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer(ourCtx);
servlet.setDefaultResponseEncoding(EncodingEnum.JSON);
servlet.setPagingProvider(new FifoMemoryPagingProvider(10));
servlet.registerProviders(Collections.singletonList(new MyGraphQLProvider()));
servlet.registerProvider(new MyPatientResourceProvider());
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
JettyUtil.startServer(ourServer);
ourPort = JettyUtil.getPortForStartedServer(ourServer);
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
@RegisterExtension
private final RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(ourCtx)
.registerProvider(new MyPatientResourceProvider())
.registerProvider(new MyGraphQLProvider());
@BeforeEach
public void before() {
ourNextRetVal = null;
ourLastId = null;
ourLastQuery = null;
ourMethodCount = 0;
ourLastResourceType = null;
}
@Test
public void testGraphInstance() throws Exception {
public void testGraphInstance_Get() throws Exception {
ourNextRetVal = "{\"foo\"}";
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$graphql?query=" + UrlUtil.escapeUrlParam("{name{family,given}}"));
HttpGet httpGet = new HttpGet("http://localhost:" + myRestfulServerExtension.getPort() + "/Patient/123/$graphql?query=" + UrlUtil.escapeUrlParam("{name{family,given}}"));
CloseableHttpResponse status = ourClient.execute(httpGet);
try {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
@ -118,12 +89,29 @@ public class GraphQLR4RawTest {
}
@Test
public void testGraphPostContentTypeJson() throws Exception {
public void testGraphInstance_Get_UnsupportedResourceType() throws Exception {
ourNextRetVal = "{\"foo\"}";
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/123/$graphql");
HttpGet httpGet = new HttpGet("http://localhost:" + myRestfulServerExtension.getPort() + "/Condition/123/$graphql?query=" + UrlUtil.escapeUrlParam("{name{family,given}}"));
CloseableHttpResponse status = ourClient.execute(httpGet);
try {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(404, status.getStatusLine().getStatusCode());
assertThat(responseContent, containsString("Unknown resource type"));
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
}
@Test
public void testGraphInstance_Post_ContentTypeJson() throws Exception {
ourNextRetVal = "{\"foo\"}";
HttpPost httpPost = new HttpPost("http://localhost:" + myRestfulServerExtension.getPort() + "/Patient/123/$graphql");
StringEntity entity = new StringEntity("{\"query\": \"{name{family,given}}\"}");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
@ -147,10 +135,10 @@ public class GraphQLR4RawTest {
}
@Test
public void testGraphPostContentTypeGraphql() throws Exception {
public void testGraphInstance_Post_ContentTypeGraphql() throws Exception {
ourNextRetVal = "{\"foo\"}";
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/123/$graphql");
HttpPost httpPost = new HttpPost("http://localhost:" + myRestfulServerExtension.getPort() + "/Patient/123/$graphql");
StringEntity entity = new StringEntity("{name{family,given}}");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
@ -166,6 +154,7 @@ public class GraphQLR4RawTest {
assertThat(status.getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue(), startsWith("application/json"));
assertEquals("Patient/123", ourLastId.getValue());
assertEquals("{name{family,given}}", ourLastQuery);
assertEquals("Patient", ourLastResourceType);
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
@ -173,31 +162,41 @@ public class GraphQLR4RawTest {
}
@Test
public void testGraphInstanceUnknownType() throws Exception {
public void testGraphBase_Post_ListQuery() throws Exception {
ourNextRetVal = "{\"foo\"}";
HttpPost httpPost = new HttpPost("http://localhost:" + myRestfulServerExtension.getPort() + "/$graphql");
StringEntity entity = new StringEntity("{\"query\": \"{PatientList(date: \\\"2022\\\") {name{family,given}}}\"}");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
httpPost.setHeader("Content-type", "application/json");
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Condition/123/$graphql?query=" + UrlUtil.escapeUrlParam("{name{family,given}}"));
CloseableHttpResponse status = ourClient.execute(httpGet);
CloseableHttpResponse status = ourClient.execute(httpPost);
try {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(responseContent);
assertEquals(404, status.getStatusLine().getStatusCode());
assertThat(responseContent, containsString("Unknown resource type"));
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("{\"foo\"}", responseContent);
assertThat(status.getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue(), startsWith("application/json"));
assertNull(ourLastId);
assertNull(ourLastResourceType);
assertEquals("{PatientList(date: \"2022\") {name{family,given}}}", ourLastQuery);
} finally {
IOUtils.closeQuietly(status.getEntity().getContent());
}
}
@Test
public void testGraphSystem() throws Exception {
ourNextRetVal = "{\"foo\"}";
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/$graphql?query=" + UrlUtil.escapeUrlParam("{name{family,given}}"));
HttpGet httpGet = new HttpGet("http://localhost:" + myRestfulServerExtension.getPort() + "/$graphql?query=" + UrlUtil.escapeUrlParam("{name{family,given}}"));
CloseableHttpResponse status = ourClient.execute(httpGet);
try {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
@ -215,20 +214,33 @@ public class GraphQLR4RawTest {
}
@AfterAll
public static void afterClassClearContext() throws Exception {
ourClient.close();
TestUtil.randomizeLocaleAndTimezone();
}
@BeforeAll
public static void beforeClass() throws Exception {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
public static class MyGraphQLProvider {
@GraphQL(type = RequestTypeEnum.GET)
public String processGet(@IdParam IdType theId, @GraphQLQueryUrl String theQuery) {
ourMethodCount++;
ourLastId = theId;
ourLastQuery = theQuery;
return ourNextRetVal;
}
@GraphQL(type = RequestTypeEnum.POST)
public String processPost(@IdParam IdType theId, @GraphQLQueryBody String theQuery) {
ourMethodCount++;
public String processPost(RequestDetails theRequestDetails, @IdParam IdType theId, @GraphQLQueryBody String theQuery) {
ourLastId = theId;
ourLastResourceType = theRequestDetails.getResourceName();
ourLastQuery = theQuery;
return ourNextRetVal;
}
@ -246,13 +258,13 @@ public class GraphQLR4RawTest {
@Search()
public List search(
@OptionalParam(name = Patient.SP_IDENTIFIER) TokenAndListParam theIdentifiers) {
ArrayList<Patient> retVal = new ArrayList<Patient>();
ArrayList<Patient> retVal = new ArrayList<>();
for (int i = 0; i < 200; i++) {
Patient patient = new Patient();
patient.addName(new HumanName().setFamily("FAMILY"));
patient.getIdElement().setValue("Patient/" + i);
retVal.add((Patient) patient);
retVal.add(patient);
}
return retVal;
}

View File

@ -3,8 +3,10 @@ package ca.uhn.fhir.util;
import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Period;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Resource;
@ -88,6 +90,53 @@ public class GraphQLEngineTest {
}
@Test
public void testChoiceType_SelectDifferentType() throws EGraphEngine, EGraphQLException, IOException {
Observation obs = new Observation();
obs.setId("http://foo.com/Patient/PATA");
obs.setEffective(new Period().setStartElement(new DateTimeType("2022-01-01T00:00:00Z")).setEndElement(new DateTimeType("2022-01-01T05:00:00Z")));
GraphQLEngine engine = new GraphQLEngine(ourWorkerCtx);
engine.setFocus(obs);
engine.setGraphQL(Parser.parse("{id, effectiveDateTime}"));
engine.execute();
GraphQLResponse output = engine.getOutput();
output.setWriteWrapper(false);
StringBuilder outputBuilder = new StringBuilder();
output.write(outputBuilder, 0, "\n");
String expected = "{\n" +
" \"id\":\"http://foo.com/Patient/PATA\"\n" +
"}";
assertEquals(TestUtil.stripReturns(expected), TestUtil.stripReturns(outputBuilder.toString()));
}
@Test
public void testChoiceType_SelectSameType() throws EGraphEngine, EGraphQLException, IOException {
Observation obs = new Observation();
obs.setId("http://foo.com/Patient/PATA");
obs.setEffective(new DateTimeType("2022-01-01T12:12:12Z"));
GraphQLEngine engine = new GraphQLEngine(ourWorkerCtx);
engine.setFocus(obs);
engine.setGraphQL(Parser.parse("{id, effectiveDateTime}"));
engine.execute();
GraphQLResponse output = engine.getOutput();
output.setWriteWrapper(false);
StringBuilder outputBuilder = new StringBuilder();
output.write(outputBuilder, 0, "\n");
String expected = "{\n" +
" \"id\":\"http://foo.com/Patient/PATA\",\n" +
" \"effectiveDateTime\":\"2022-01-01T12:12:12Z\"\n" +
"}";
assertEquals(TestUtil.stripReturns(expected), TestUtil.stripReturns(outputBuilder.toString()));
}
@Test
public void testReferences() throws EGraphQLException, EGraphEngine, IOException, FHIRException {
@ -128,7 +177,7 @@ public class GraphQLEngineTest {
@BeforeAll
public static void beforeClass() {
ourCtx = FhirContext.forR4();
ourCtx = FhirContext.forR4Cached();
ourWorkerCtx = new HapiWorkerContext(ourCtx, ourCtx.getValidationSupport());
}

View File

@ -0,0 +1,184 @@
package org.hl7.fhir.r5.utils;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r5.model.DateTimeType;
import org.hl7.fhir.r5.model.Observation;
import org.hl7.fhir.r5.model.Patient;
import org.hl7.fhir.r5.model.Period;
import org.hl7.fhir.r5.model.Quantity;
import org.hl7.fhir.r5.model.Reference;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.utilities.graphql.EGraphEngine;
import org.hl7.fhir.utilities.graphql.EGraphQLException;
import org.hl7.fhir.utilities.graphql.GraphQLResponse;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.hl7.fhir.utilities.graphql.Parser;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class GraphQLEngineTest {
private static HapiWorkerContext ourWorkerCtx;
private static FhirContext ourCtx;
private org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GraphQLEngineTest.class);
private Observation createObservation() {
Observation obs = new Observation();
obs.setId("http://foo.com/Patient/PATA");
obs.setValue(new Quantity().setValue(123).setUnit("cm"));
obs.setSubject(new Reference("Patient/123"));
return obs;
}
private IGraphQLStorageServices createStorageServices() throws FHIRException {
IGraphQLStorageServices retVal = mock(IGraphQLStorageServices.class);
when(retVal.lookup(nullable(Object.class), nullable(Resource.class), nullable(Reference.class))).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) {
Object appInfo = invocation.getArguments()[0];
Resource context = (Resource) invocation.getArguments()[1];
Reference reference = (Reference) invocation.getArguments()[2];
ourLog.info("AppInfo: {} / Context: {} / Reference: {}", appInfo, context.getId(), reference.getReference());
if (reference.getReference().equalsIgnoreCase("Patient/123")) {
Patient p = new Patient();
p.getBirthDateElement().setValueAsString("2011-02-22");
return new IGraphQLStorageServices.ReferenceResolution(context, p);
}
ourLog.info("Not found!");
return null;
}
});
return retVal;
}
@Test
public void testGraphSimple() throws EGraphQLException, EGraphEngine, IOException, FHIRException {
Observation obs = createObservation();
GraphQLEngine engine = new GraphQLEngine(ourWorkerCtx);
engine.setFocus(obs);
engine.setGraphQL(Parser.parse("{valueQuantity{value,unit}}"));
engine.execute();
GraphQLResponse output = engine.getOutput();
output.setWriteWrapper(false);
StringBuilder outputBuilder = new StringBuilder();
output.write(outputBuilder, 0, "\n");
String expected = "{\n" +
" \"valueQuantity\":{\n" +
" \"value\":123,\n" +
" \"unit\":\"cm\"\n" +
" }\n" +
"}";
assertEquals(TestUtil.stripReturns(expected), TestUtil.stripReturns(outputBuilder.toString()));
}
@Test
public void testChoiceType_SelectDifferentType() throws EGraphEngine, EGraphQLException, IOException {
Observation obs = new Observation();
obs.setId("http://foo.com/Patient/PATA");
obs.setEffective(new Period().setStartElement(new DateTimeType("2022-01-01T00:00:00Z")).setEndElement(new DateTimeType("2022-01-01T05:00:00Z")));
GraphQLEngine engine = new GraphQLEngine(ourWorkerCtx);
engine.setFocus(obs);
engine.setGraphQL(Parser.parse("{id, effectiveDateTime}"));
engine.execute();
GraphQLResponse output = engine.getOutput();
output.setWriteWrapper(false);
StringBuilder outputBuilder = new StringBuilder();
output.write(outputBuilder, 0, "\n");
String expected = "{\n" +
" \"id\":\"http://foo.com/Patient/PATA\"\n" +
"}";
assertEquals(TestUtil.stripReturns(expected), TestUtil.stripReturns(outputBuilder.toString()));
}
@Test
public void testChoiceType_SelectSameType() throws EGraphEngine, EGraphQLException, IOException {
Observation obs = new Observation();
obs.setId("http://foo.com/Patient/PATA");
obs.setEffective(new DateTimeType("2022-01-01T12:12:12Z"));
GraphQLEngine engine = new GraphQLEngine(ourWorkerCtx);
engine.setFocus(obs);
engine.setGraphQL(Parser.parse("{id, effectiveDateTime}"));
engine.execute();
GraphQLResponse output = engine.getOutput();
output.setWriteWrapper(false);
StringBuilder outputBuilder = new StringBuilder();
output.write(outputBuilder, 0, "\n");
String expected = "{\n" +
" \"id\":\"http://foo.com/Patient/PATA\",\n" +
" \"effectiveDateTime\":\"2022-01-01T12:12:12Z\"\n" +
"}";
assertEquals(TestUtil.stripReturns(expected), TestUtil.stripReturns(outputBuilder.toString()));
}
@Test
public void testReferences() throws EGraphQLException, EGraphEngine, IOException, FHIRException {
String graph = " { \n" +
" id\n" +
" subject { \n" +
" reference\n" +
" resource(type : Patient) { birthDate }\n" +
" resource(type : Practioner) { practitionerRole { speciality } }\n" +
" } \n" +
" code {coding {system code} }\n" +
" }\n" +
" ";
GraphQLEngine engine = new GraphQLEngine(ourWorkerCtx);
engine.setFocus(createObservation());
engine.setGraphQL(Parser.parse(graph));
engine.setServices(createStorageServices());
engine.execute();
GraphQLResponse output = engine.getOutput();
output.setWriteWrapper(false);
StringBuilder outputBuilder = new StringBuilder();
output.write(outputBuilder, 0, "\n");
String expected = "{\n" +
" \"id\":\"http://foo.com/Patient/PATA\",\n" +
" \"subject\":{\n" +
" \"reference\":\"Patient/123\",\n" +
" \"resource\":{\n" +
" \"birthDate\":\"2011-02-22\"\n" +
" }\n" +
" }\n" +
"}";
assertEquals(TestUtil.stripReturns(expected), TestUtil.stripReturns(outputBuilder.toString()));
}
@BeforeAll
public static void beforeClass() {
ourCtx = FhirContext.forR5Cached();
ourWorkerCtx = new HapiWorkerContext(ourCtx, ourCtx.getValidationSupport());
}
}

16
pom.xml
View File

@ -45,15 +45,6 @@
</scm>
<repositories>
<!--
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
-->
<repository>
<id>oss-snapshot</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
@ -765,7 +756,7 @@
<properties>
<fhir_core_version>5.6.27</fhir_core_version>
<fhir_core_version>5.6.35</fhir_core_version>
<ucum_version>1.0.3</ucum_version>
<surefire_jvm_args>-Dfile.encoding=UTF-8 -Xmx2048m</surefire_jvm_args>
@ -924,6 +915,11 @@
<artifactId>caffeine</artifactId>
<version>${caffeine_version}</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java</artifactId>
<version>17.3</version>
</dependency>
<!-- mail start -->
<dependency>
<groupId>org.simplejavamail</groupId>