Work on graph QL support
This commit is contained in:
parent
1a95ba3b65
commit
5413b276af
|
@ -0,0 +1,15 @@
|
|||
package ca.uhn.fhir.rest.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* A method annotated with this annotation will be treated as a GraphQL implementation
|
||||
* method
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(value= ElementType.METHOD)
|
||||
public @interface GraphQL {
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package ca.uhn.fhir.rest.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* This annotation should be placed on the parameter of a
|
||||
* {@link GraphQL @GraphQL} annotated method. The given
|
||||
* parameter will be populated with the specific graphQL
|
||||
* query being requested.
|
||||
*
|
||||
* <p>
|
||||
* This parameter should be of type {@link String}
|
||||
* </p>
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.PARAMETER)
|
||||
public @interface GraphQLQuery {
|
||||
// ignore
|
||||
}
|
|
@ -79,5 +79,5 @@ public @interface Operation {
|
|||
* bundle type to set in the bundle.
|
||||
*/
|
||||
BundleTypeEnum bundleType() default BundleTypeEnum.COLLECTION;
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -171,6 +171,7 @@ public class Constants {
|
|||
public static final String URL_TOKEN_HISTORY = "_history";
|
||||
public static final String URL_TOKEN_METADATA = "metadata";
|
||||
public static final String OO_INFOSTATUS_PROCESSING = "processing";
|
||||
public static final String PARAM_GRAPHQL_QUERY = "query";
|
||||
|
||||
static {
|
||||
CHARSET_UTF8 = Charset.forName(CHARSET_NAME_UTF8);
|
||||
|
|
|
@ -37,6 +37,14 @@ public enum RestOperationTypeEnum {
|
|||
|
||||
GET_PAGE("get-page"),
|
||||
|
||||
/**
|
||||
* <b>
|
||||
* Use this value with caution, this may
|
||||
* change as the GraphQL interface matures
|
||||
* </b>
|
||||
*/
|
||||
GRAPHQL_REQUEST("graphql-request"),
|
||||
|
||||
/**
|
||||
* E.g. $everything, $validate, etc.
|
||||
*/
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
package ca.uhn.fhir.rest.api.server;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.model.api.IFhirVersion;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.IServerConformanceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
/**
|
||||
* This class is the server specific equivalent to {@link IFhirVersion}
|
||||
|
@ -15,5 +14,5 @@ public interface IFhirVersionServer {
|
|||
IServerConformanceProvider<? extends IBaseResource> createServerConformanceProvider(RestfulServer theRestfulServer);
|
||||
|
||||
IResourceProvider createServerProfilesProvider(RestfulServer theRestfulServer);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -543,7 +543,7 @@ public class RestfulServerUtils {
|
|||
public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, Set<SummaryEnum> theSummaryMode, int theStausCode, String theStatusMessage,
|
||||
boolean theAddContentLocationHeader, boolean respondGzip, RequestDetails theRequestDetails, IIdType theOperationResourceId, IPrimitiveType<Date> theOperationResourceLastUpdated)
|
||||
throws IOException {
|
||||
IRestfulResponse restUtil = theRequestDetails.getResponse();
|
||||
IRestfulResponse response = theRequestDetails.getResponse();
|
||||
|
||||
// Determine response encoding
|
||||
ResponseEncoding responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequestDetails, theServer.getDefaultResponseEncoding());
|
||||
|
@ -561,14 +561,14 @@ public class RestfulServerUtils {
|
|||
|
||||
if (theAddContentLocationHeader && fullId != null) {
|
||||
if (theServer.getFhirContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
|
||||
restUtil.addHeader(Constants.HEADER_CONTENT_LOCATION, fullId.getValue());
|
||||
response.addHeader(Constants.HEADER_CONTENT_LOCATION, fullId.getValue());
|
||||
}
|
||||
restUtil.addHeader(Constants.HEADER_LOCATION, fullId.getValue());
|
||||
response.addHeader(Constants.HEADER_LOCATION, fullId.getValue());
|
||||
}
|
||||
|
||||
if (theServer.getETagSupport() == ETagSupportEnum.ENABLED) {
|
||||
if (fullId != null && fullId.hasVersionIdPart()) {
|
||||
restUtil.addHeader(Constants.HEADER_ETAG, "W/\"" + fullId.getVersionIdPart() + '"');
|
||||
response.addHeader(Constants.HEADER_ETAG, "W/\"" + fullId.getVersionIdPart() + '"');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -582,9 +582,9 @@ public class RestfulServerUtils {
|
|||
}
|
||||
// Force binary resources to download - This is a security measure to prevent
|
||||
// malicious images or HTML blocks being served up as content.
|
||||
restUtil.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;");
|
||||
response.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;");
|
||||
|
||||
return restUtil.sendAttachmentResponse(bin, theStausCode, contentType);
|
||||
return response.sendAttachmentResponse(bin, theStausCode, contentType);
|
||||
}
|
||||
|
||||
// Ok, we're not serving a binary resource, so apply default encoding
|
||||
|
@ -615,7 +615,7 @@ public class RestfulServerUtils {
|
|||
lastUpdated = extractLastUpdatedFromResource(theResource);
|
||||
}
|
||||
if (lastUpdated != null && lastUpdated.isEmpty() == false) {
|
||||
restUtil.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated.getValue()));
|
||||
response.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated.getValue()));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -631,7 +631,7 @@ public class RestfulServerUtils {
|
|||
}
|
||||
String charset = Constants.CHARSET_NAME_UTF8;
|
||||
|
||||
Writer writer = restUtil.getResponseWriter(theStausCode, theStatusMessage, contentType, charset, respondGzip);
|
||||
Writer writer = response.getResponseWriter(theStausCode, theStatusMessage, contentType, charset, respondGzip);
|
||||
if (theResource == null) {
|
||||
// No response is being returned
|
||||
} else if (encodingDomainResourceAsText && theResource instanceof IResource) {
|
||||
|
@ -641,7 +641,7 @@ public class RestfulServerUtils {
|
|||
parser.encodeResourceToWriter(theResource, writer);
|
||||
}
|
||||
//FIXME resource leak
|
||||
return restUtil.sendWriterResponse(theStausCode, contentType, charset, writer);
|
||||
return response.sendWriterResponse(theStausCode, contentType, charset, writer);
|
||||
}
|
||||
|
||||
public static Integer tryToExtractNamedParameter(RequestDetails theRequest, String theParamName) {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package ca.uhn.fhir.rest.server.graphql;
|
||||
|
||||
import ca.uhn.fhir.rest.annotation.Operation;
|
||||
|
||||
public class GraphQLProviderR4 {
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -92,25 +92,15 @@ public abstract class BaseMethodBinding<T> {
|
|||
return parser;
|
||||
}
|
||||
|
||||
protected IParser createAppropriateParserForParsingServerRequest(RequestDetails theRequest) {
|
||||
String contentTypeHeader = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
|
||||
EncodingEnum encoding;
|
||||
if (isBlank(contentTypeHeader)) {
|
||||
encoding = EncodingEnum.XML;
|
||||
} else {
|
||||
int semicolon = contentTypeHeader.indexOf(';');
|
||||
if (semicolon != -1) {
|
||||
contentTypeHeader = contentTypeHeader.substring(0, semicolon);
|
||||
protected Object[] createMethodParams(RequestDetails theRequest) {
|
||||
Object[] params = new Object[getParameters().size()];
|
||||
for (int i = 0; i < getParameters().size(); i++) {
|
||||
IParameter param = getParameters().get(i);
|
||||
if (param != null) {
|
||||
params[i] = param.translateQueryParametersIntoServerArgument(theRequest, this);
|
||||
}
|
||||
encoding = EncodingEnum.forContentType(contentTypeHeader);
|
||||
}
|
||||
|
||||
if (encoding == null) {
|
||||
throw new InvalidRequestException("Request contins non-FHIR conent-type header value: " + contentTypeHeader);
|
||||
}
|
||||
|
||||
IParser parser = encoding.newParser(getContext());
|
||||
return parser;
|
||||
return params;
|
||||
}
|
||||
|
||||
protected Object[] createParametersForServerRequest(RequestDetails theRequest) {
|
||||
|
@ -345,16 +335,21 @@ public abstract class BaseMethodBinding<T> {
|
|||
Operation operation = theMethod.getAnnotation(Operation.class);
|
||||
GetPage getPage = theMethod.getAnnotation(GetPage.class);
|
||||
Patch patch = theMethod.getAnnotation(Patch.class);
|
||||
GraphQL graphQL = theMethod.getAnnotation(GraphQL.class);
|
||||
|
||||
// ** if you add another annotation above, also add it to the next line:
|
||||
if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance, create, update, delete, history, validate, addTags, deleteTags, transaction, operation, getPage, patch)) {
|
||||
if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance, create, update, delete, history, validate, addTags, deleteTags, transaction, operation, getPage, patch, graphQL)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (getPage != null) {
|
||||
return new PageMethodBinding(theContext, theMethod);
|
||||
}
|
||||
|
||||
|
||||
if (graphQL != null) {
|
||||
return new GraphQLMethodBinding(theMethod, theContext, theProvider);
|
||||
}
|
||||
|
||||
Class<? extends IBaseResource> returnType;
|
||||
|
||||
Class<? extends IBaseResource> returnTypeFromRp = null;
|
||||
|
|
|
@ -170,14 +170,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
|
|||
}
|
||||
|
||||
public IBaseResource doInvokeServer(IRestfulServer<?> theServer, RequestDetails theRequest) {
|
||||
// Method params
|
||||
Object[] params = new Object[getParameters().size()];
|
||||
for (int i = 0; i < getParameters().size(); i++) {
|
||||
IParameter param = getParameters().get(i);
|
||||
if (param != null) {
|
||||
params[i] = param.translateQueryParametersIntoServerArgument(theRequest, this);
|
||||
}
|
||||
}
|
||||
Object[] params = createMethodParams(theRequest);
|
||||
|
||||
Object resultObj = invokeServer(theServer, theRequest, params);
|
||||
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package ca.uhn.fhir.rest.server.method;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.IRestfulServer;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class GraphQLMethodBinding extends BaseMethodBinding<String> {
|
||||
|
||||
public GraphQLMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
|
||||
super(theMethod, theContext, theProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResourceName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestOperationTypeEnum getRestOperationType() {
|
||||
return RestOperationTypeEnum.GRAPHQL_REQUEST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||
if ("graphql".equals(theRequest.getOperation())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
|
||||
Object[] methodParams = createMethodParams(theRequest);
|
||||
Object response = invokeServerMethod(theServer, theRequest, methodParams);
|
||||
|
||||
int statusCode = Constants.STATUS_HTTP_200_OK;
|
||||
String statusMessage = Constants.HTTP_STATUS_NAMES.get(statusCode);
|
||||
String contentType = Constants.CT_JSON;
|
||||
String charset = Constants.CHARSET_NAME_UTF8;
|
||||
boolean respondGzip = theRequest.isRespondGzip();
|
||||
|
||||
Writer writer = theRequest.getResponse().getResponseWriter(statusCode, statusMessage, contentType, charset, respondGzip);
|
||||
|
||||
String responseString = (String) response;
|
||||
writer.write(responseString);
|
||||
writer.close();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package ca.uhn.fhir.rest.server.method;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2017 University Health Network
|
||||
* %%
|
||||
* 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.ConfigurationException;
|
||||
import ca.uhn.fhir.model.primitive.IntegerDt;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.annotation.Count;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
|
||||
public class GraphQLQueryParameter implements IParameter {
|
||||
|
||||
private Class<?> myType;
|
||||
|
||||
@Override
|
||||
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
|
||||
String[] queryParams = theRequest.getParameters().get(Constants.PARAM_GRAPHQL_QUERY);
|
||||
String retVal = null;
|
||||
if (queryParams != null) {
|
||||
if (queryParams.length > 0) {
|
||||
retVal = queryParams[0];
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
|
||||
if (theOuterCollectionType != null) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" +theMethod.getDeclaringClass().getCanonicalName()+ "' is annotated with @" + Count.class.getName() + " but can not be of collection type");
|
||||
}
|
||||
if (!String.class.equals(theParameterType)) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" +theMethod.getDeclaringClass().getCanonicalName()+ "' is annotated with @" + Count.class.getName() + " but type '" + theParameterType + "' is an invalid type, must be one of Integer or IntegerType");
|
||||
}
|
||||
myType = theParameterType;
|
||||
}
|
||||
|
||||
}
|
|
@ -163,6 +163,8 @@ public class MethodUtil {
|
|||
((AtParameter) param).setType(theContext, parameterType, innerCollectionType, outerCollectionType);
|
||||
} else if (nextAnnotation instanceof Count) {
|
||||
param = new CountParameter();
|
||||
} else if (nextAnnotation instanceof GraphQLQuery) {
|
||||
param = new GraphQLQueryParameter();
|
||||
} else if (nextAnnotation instanceof Sort) {
|
||||
param = new SortParameter(theContext);
|
||||
} else if (nextAnnotation instanceof TransactionParam) {
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
package org.hl7.fhir.r4.hapi.ctx;
|
||||
|
||||
import org.hl7.fhir.r4.hapi.rest.server.ServerCapabilityStatementProvider;
|
||||
import org.hl7.fhir.r4.hapi.rest.server.ServerProfileProvider;
|
||||
|
||||
import ca.uhn.fhir.rest.api.server.IFhirVersionServer;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import org.hl7.fhir.r4.hapi.rest.server.ServerCapabilityStatementProvider;
|
||||
import org.hl7.fhir.r4.hapi.rest.server.ServerProfileProvider;
|
||||
|
||||
public class FhirServerR4 implements IFhirVersionServer {
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.annotation.OptionalParam;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
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.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.model.HumanName;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class GraphQLR4ProviderTest {
|
||||
|
||||
private static CloseableHttpClient ourClient;
|
||||
private static FhirContext ourCtx = FhirContext.forR4();
|
||||
private static TokenAndListParam ourIdentifiers;
|
||||
private static String ourLastMethod;
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GraphQLR4ProviderTest.class);
|
||||
private static int ourPort;
|
||||
private static Server ourServer;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
ourLastMethod = null;
|
||||
ourIdentifiers = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchNormal() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar");
|
||||
CloseableHttpResponse status = ourClient.execute(httpGet);
|
||||
try {
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
|
||||
assertEquals("search", ourLastMethod);
|
||||
|
||||
assertEquals("foo", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getSystem());
|
||||
assertEquals("bar", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue());
|
||||
} finally {
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
ourServer.stop();
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
ourPort = PortUtil.findFreePort();
|
||||
ourServer = new Server(ourPort);
|
||||
|
||||
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
RestfulServer servlet = new RestfulServer(ourCtx);
|
||||
servlet.setDefaultResponseEncoding(EncodingEnum.JSON);
|
||||
servlet.setPagingProvider(new FifoMemoryPagingProvider(10));
|
||||
|
||||
servlet.setResourceProviders(patientProvider);
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||
ourServer.setHandler(proxyHandler);
|
||||
ourServer.start();
|
||||
|
||||
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||
builder.setConnectionManager(connectionManager);
|
||||
ourClient = builder.build();
|
||||
|
||||
}
|
||||
|
||||
public static class DummyPatientResourceProvider implements IResourceProvider {
|
||||
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Search()
|
||||
public List search(
|
||||
@OptionalParam(name = Patient.SP_IDENTIFIER) TokenAndListParam theIdentifiers) {
|
||||
ourLastMethod = "search";
|
||||
ourIdentifiers = theIdentifiers;
|
||||
ArrayList<Patient> retVal = new ArrayList<Patient>();
|
||||
|
||||
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);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
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.util.PortUtil;
|
||||
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.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;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
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 String ourNextRetVal;
|
||||
private static IdType ourLastId;
|
||||
private static String ourLastQuery;
|
||||
private static int ourMethodCount;
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() throws Exception {
|
||||
ourServer.stop();
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
ourPort = PortUtil.findFreePort();
|
||||
ourServer = new Server(ourPort);
|
||||
|
||||
MyProvider provider = new MyProvider();
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
RestfulServer servlet = new RestfulServer(ourCtx);
|
||||
servlet.setDefaultResponseEncoding(EncodingEnum.JSON);
|
||||
servlet.setPagingProvider(new FifoMemoryPagingProvider(10));
|
||||
|
||||
servlet.registerProvider(provider);
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||
ourServer.setHandler(proxyHandler);
|
||||
ourServer.start();
|
||||
|
||||
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||
builder.setConnectionManager(connectionManager);
|
||||
ourClient = builder.build();
|
||||
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
ourNextRetVal = null;
|
||||
ourLastId = null;
|
||||
ourLastQuery = null;
|
||||
ourMethodCount = 0;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGraphInstance() throws Exception {
|
||||
ourNextRetVal = "{\"foo\"}";
|
||||
|
||||
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/$graphql?query=" + UrlUtil.escape("{name{family,given}}"));
|
||||
CloseableHttpResponse status = ourClient.execute(httpGet);
|
||||
try {
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
|
||||
assertEquals("{\"foo\"}", responseContent);
|
||||
assertEquals("application/json", status.getFirstHeader(Constants.HEADER_CONTENT_TYPE));
|
||||
assertEquals("Patient/123", ourLastId.getValue());
|
||||
assertEquals("{name{family,given}}", ourLastQuery);
|
||||
|
||||
} finally {
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class MyProvider {
|
||||
|
||||
|
||||
@GraphQL
|
||||
public String process(@IdParam IdType theId, @GraphQLQuery String theQuery) {
|
||||
ourMethodCount++;
|
||||
ourLastId = theId;
|
||||
ourLastQuery = theQuery;
|
||||
return ourNextRetVal;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
package ca.uhn.fhir.util;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport;
|
||||
import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.hl7.fhir.r4.utils.GraphQLEngine;
|
||||
import org.hl7.fhir.utilities.graphql.EGraphEngine;
|
||||
import org.hl7.fhir.utilities.graphql.EGraphQLException;
|
||||
import org.hl7.fhir.utilities.graphql.ObjectValue;
|
||||
import org.hl7.fhir.utilities.graphql.Parser;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Matchers.any;
|
||||
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);
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
ourCtx = FhirContext.forR4();
|
||||
ourWorkerCtx = new HapiWorkerContext(ourCtx, new DefaultProfileValidationSupport());
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
ObjectValue output = engine.getOutput();
|
||||
StringBuilder outputBuilder = new StringBuilder();
|
||||
output.write(outputBuilder, 0, "\n");
|
||||
|
||||
String expected = "{\n" +
|
||||
" \"valueQuantity\":{\n" +
|
||||
" \"value\":123,\n" +
|
||||
" \"unit\":\"cm\"\n" +
|
||||
" }\n" +
|
||||
"}";
|
||||
assertEquals(expected, outputBuilder.toString());
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
ObjectValue output = engine.getOutput();
|
||||
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(expected, outputBuilder.toString());
|
||||
|
||||
}
|
||||
|
||||
private GraphQLEngine.IGraphQLStorageServices createStorageServices() throws FHIRException {
|
||||
GraphQLEngine.IGraphQLStorageServices retVal = mock(GraphQLEngine.IGraphQLStorageServices.class);
|
||||
when(retVal.lookup(any(Object.class), any(Resource.class), any(Reference.class))).thenAnswer(new Answer<Object>() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
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 GraphQLEngine.IGraphQLStorageServices.ReferenceResolution(context, p);
|
||||
}
|
||||
|
||||
ourLog.info("Not found!");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,65 +1,79 @@
|
|||
package org.hl7.fhir.utilities.graphql;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public class ObjectValue extends Value {
|
||||
private List<Argument> fields = new ArrayList<Argument>();
|
||||
|
||||
public ObjectValue() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ObjectValue(JsonObject json) throws EGraphQLException {
|
||||
super();
|
||||
for (Entry<String, JsonElement> n : json.entrySet())
|
||||
fields.add(new Argument(n.getKey(), n.getValue()));
|
||||
}
|
||||
|
||||
public List<Argument> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public Argument addField(String name, boolean isList) {
|
||||
Argument result = null;
|
||||
for (Argument t : fields)
|
||||
if ((t.name.equals(name)))
|
||||
result = t;
|
||||
if (result == null) {
|
||||
result = new Argument();
|
||||
result.setName(name);
|
||||
result.setList(isList);
|
||||
fields.add(result);
|
||||
} else
|
||||
result.list = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
public void write(StringBuilder b, int indent) throws EGraphQLException, EGraphEngine {
|
||||
b.append("{");
|
||||
int ni = indent;
|
||||
String s = "";
|
||||
String se = "";
|
||||
if ((ni > -1))
|
||||
{
|
||||
se = "\r\n"+Utilities.padLeft("",' ', ni*2);
|
||||
ni++;
|
||||
s = "\r\n"+Utilities.padLeft("",' ', ni*2);
|
||||
}
|
||||
boolean first = true;
|
||||
for (Argument a : fields) {
|
||||
if (first) first = false; else b.append(",");
|
||||
b.append(s);
|
||||
a.write(b, ni);
|
||||
}
|
||||
b.append(se);
|
||||
b.append("}");
|
||||
|
||||
}
|
||||
}
|
||||
package org.hl7.fhir.utilities.graphql;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public class ObjectValue extends Value {
|
||||
private List<Argument> fields = new ArrayList<Argument>();
|
||||
|
||||
public ObjectValue() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ObjectValue(JsonObject json) throws EGraphQLException {
|
||||
super();
|
||||
for (Entry<String, JsonElement> n : json.entrySet())
|
||||
fields.add(new Argument(n.getKey(), n.getValue()));
|
||||
}
|
||||
|
||||
public List<Argument> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public Argument addField(String name, boolean isList) {
|
||||
Argument result = null;
|
||||
for (Argument t : fields)
|
||||
if ((t.name.equals(name)))
|
||||
result = t;
|
||||
if (result == null) {
|
||||
result = new Argument();
|
||||
result.setName(name);
|
||||
result.setList(isList);
|
||||
fields.add(result);
|
||||
} else
|
||||
result.list = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the output using the system default line separator (as defined in {@link System#lineSeparator}
|
||||
* @param b The StringBuilder to populate
|
||||
* @param indent The indent level, or <code>-1</code> for no indent
|
||||
*/
|
||||
public void write(StringBuilder b, int indent) throws EGraphQLException, EGraphEngine {
|
||||
write(b, indent, System.lineSeparator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the output using the system default line separator (as defined in {@link System#lineSeparator}
|
||||
* @param b The StringBuilder to populate
|
||||
* @param indent The indent level, or <code>-1</code> for no indent
|
||||
* @param lineSeparator The line separator
|
||||
*/
|
||||
public void write(StringBuilder b, Integer indent, String lineSeparator) throws EGraphQLException, EGraphEngine {
|
||||
b.append("{");
|
||||
String s = "";
|
||||
String se = "";
|
||||
if ((indent > -1))
|
||||
{
|
||||
se = lineSeparator + Utilities.padLeft("",' ', indent*2);
|
||||
indent++;
|
||||
s = lineSeparator + Utilities.padLeft("",' ', indent*2);
|
||||
}
|
||||
boolean first = true;
|
||||
for (Argument a : fields) {
|
||||
if (first) first = false; else b.append(",");
|
||||
b.append(s);
|
||||
a.write(b, indent);
|
||||
}
|
||||
b.append(se);
|
||||
b.append("}");
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue