Allow operations to better work across multiple versions of FHIR
This commit is contained in:
parent
d03fc0f61d
commit
8158292665
|
@ -76,6 +76,14 @@ public @interface OperationParam {
|
|||
*/
|
||||
Class<? extends IBase> type() default IBase.class;
|
||||
|
||||
/**
|
||||
* Optionally specifies the type of the parameter as a string, such as <code>Coding</code> or
|
||||
* <code>base64Binary</code>. This can be useful if you want to use a generic interface type
|
||||
* on the actual method,such as {@link org.hl7.fhir.instance.model.api.IPrimitiveType} or
|
||||
* {@link @org.hl7.fhir.instance.model.api.ICompositeType}.
|
||||
*/
|
||||
String typeName() default "";
|
||||
|
||||
/**
|
||||
* The minimum number of repetitions allowed for this child (default is 0)
|
||||
*/
|
||||
|
|
|
@ -219,7 +219,15 @@ public class MethodUtil {
|
|||
param = new ConditionalParamBinder(theRestfulOperationTypeEnum, ((ConditionalUrlParam) nextAnnotation).supportsMultiple());
|
||||
} else if (nextAnnotation instanceof OperationParam) {
|
||||
Operation op = theMethod.getAnnotation(Operation.class);
|
||||
param = new OperationParameter(theContext, op.name(), ((OperationParam) nextAnnotation));
|
||||
OperationParam operationParam = (OperationParam) nextAnnotation;
|
||||
param = new OperationParameter(theContext, op.name(), operationParam);
|
||||
if (isNotBlank(operationParam.typeName())) {
|
||||
Class<?> newParameterType = theContext.getElementDefinition(operationParam.typeName()).getImplementingClass();
|
||||
if (!parameterType.isAssignableFrom(newParameterType)) {
|
||||
throw new ConfigurationException("Non assignable parameter typeName=\"" + operationParam.typeName() + "\" specified on method " + theMethod);
|
||||
}
|
||||
parameterType = newParameterType;
|
||||
}
|
||||
} else if (nextAnnotation instanceof Validate.Mode) {
|
||||
if (parameterType.equals(ValidationModeEnum.class) == false) {
|
||||
throw new ConfigurationException(
|
||||
|
|
|
@ -151,12 +151,10 @@ public class OperationParameter implements IParameter {
|
|||
|
||||
boolean typeIsConcrete = !myParameterType.isInterface() && !Modifier.isAbstract(myParameterType.getModifiers());
|
||||
|
||||
//@formatter:off
|
||||
boolean isSearchParam =
|
||||
IQueryParameterType.class.isAssignableFrom(myParameterType) ||
|
||||
IQueryParameterOr.class.isAssignableFrom(myParameterType) ||
|
||||
IQueryParameterAnd.class.isAssignableFrom(myParameterType);
|
||||
//@formatter:off
|
||||
|
||||
/*
|
||||
* Note: We say here !IBase.class.isAssignableFrom because a bunch of DSTU1/2 datatypes also
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.Operation;
|
||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
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.HttpPost;
|
||||
import org.apache.http.entity.ContentType;
|
||||
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.instance.model.api.ICompositeType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.junit.*;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class OperationGenericServer2R4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationGenericServer2R4Test.class);
|
||||
private static CloseableHttpClient ourClient;
|
||||
private static FhirContext ourCtx;
|
||||
private static IdType ourLastId;
|
||||
private static Object ourLastParam1;
|
||||
private static Object ourLastParam2;
|
||||
private static Parameters ourLastResourceParam;
|
||||
private int myPort;
|
||||
private Server myServer;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
ourLastParam1 = null;
|
||||
ourLastParam2 = null;
|
||||
ourLastId = null;
|
||||
ourLastResourceParam = null;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDeclarativeTypedParameters() throws Exception {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
class PatientProvider implements IResourceProvider {
|
||||
|
||||
@Override
|
||||
public Class<Patient> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
|
||||
@Operation(name = "$OP_INSTANCE")
|
||||
public Parameters opInstance(
|
||||
@ResourceParam() IBaseResource theResourceParam,
|
||||
@IdParam IdType theId,
|
||||
@OperationParam(name = "PARAM1", typeName = "code") IPrimitiveType<String> theParam1,
|
||||
@OperationParam(name = "PARAM2", typeName = "Coding") ICompositeType theParam2
|
||||
) {
|
||||
|
||||
ourLastId = theId;
|
||||
ourLastParam1 = theParam1;
|
||||
ourLastParam2 = theParam2;
|
||||
ourLastResourceParam = (Parameters) theResourceParam;
|
||||
|
||||
Parameters retVal = new Parameters();
|
||||
retVal.addParameter().setName("RET1").setValue(new StringType("RETVAL1"));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PatientProvider provider = new PatientProvider();
|
||||
startServer(provider);
|
||||
|
||||
Parameters p = new Parameters();
|
||||
p.addParameter().setName("PARAM1").setValue(new CodeType("PARAM1val"));
|
||||
p.addParameter().setName("PARAM2").setValue(new Coding("sys", "val", "dis"));
|
||||
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||
|
||||
HttpPost httpPost = new HttpPost("http://localhost:" + myPort + "/Patient/123/$OP_INSTANCE");
|
||||
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpPost)) {
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
String response = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
ourLog.info(response);
|
||||
status.getEntity().getContent().close();
|
||||
|
||||
CodeType param1 = (CodeType) ourLastParam1;
|
||||
assertEquals("PARAM1val", param1.getValue());
|
||||
|
||||
Coding param2 = (Coding) ourLastParam2;
|
||||
assertEquals("sys", param2.getSystem());
|
||||
assertEquals("val", param2.getCode());
|
||||
assertEquals("dis", param2.getDisplay());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeclarativeTypedParametersInvalid() throws Exception {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
class PatientProvider implements IResourceProvider {
|
||||
|
||||
@Override
|
||||
public Class<Patient> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
|
||||
@Operation(name = "$OP_INSTANCE")
|
||||
public Parameters opInstance(
|
||||
@OperationParam(name = "PARAM2", typeName = "code") ICompositeType theParam2
|
||||
) {
|
||||
return new Parameters();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
PatientProvider provider = new PatientProvider();
|
||||
startServer(provider);
|
||||
fail();
|
||||
} catch (ServletException e) {
|
||||
ConfigurationException ce = (ConfigurationException) e.getCause();
|
||||
assertEquals("Failure scanning class PatientProvider: Non assignable parameter typeName=\"code\" specified on method public org.hl7.fhir.r4.model.Parameters ca.uhn.fhir.rest.server.OperationGenericServer2R4Test$2PatientProvider.opInstance(org.hl7.fhir.instance.model.api.ICompositeType)", ce.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void startServer(Object theProvider) throws Exception {
|
||||
myServer = new Server(0);
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
RestfulServer servlet = new RestfulServer(ourCtx);
|
||||
servlet.setDefaultResponseEncoding(EncodingEnum.XML);
|
||||
|
||||
servlet.setPagingProvider(new FifoMemoryPagingProvider(10).setDefaultPageSize(2));
|
||||
|
||||
servlet.setFhirContext(ourCtx);
|
||||
servlet.registerProvider(theProvider);
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||
myServer.setHandler(proxyHandler);
|
||||
JettyUtil.startServer(myServer);
|
||||
myPort = JettyUtil.getPortForStartedServer(myServer);
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
JettyUtil.closeServer(myServer);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() {
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
ourCtx = FhirContext.forR4();
|
||||
|
||||
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||
builder.setConnectionManager(connectionManager);
|
||||
ourClient = builder.build();
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -310,6 +310,11 @@
|
|||
apply to all resource types (or instances of all resource types) if they
|
||||
are found on a plain provider.
|
||||
</action>
|
||||
<action type="add">
|
||||
@Operation method parameters may now declare their type via a String name such as
|
||||
"code" or "Coding" in an attribute in @OperationParam. This is useful if you want
|
||||
to make operation methods that can operate across different versions of FHIR.
|
||||
</action>
|
||||
<action type="add">
|
||||
A new resource provider for JPA servers called
|
||||
<![CDATA[<code>BinaryAccessProvider</code>]]>
|
||||
|
@ -335,7 +340,6 @@
|
|||
a narrative on an untitled DiagnosticReport were fixed. Thanks to GitHub
|
||||
user @navyflower for reporting!
|
||||
</action>
|
||||
|
||||
</release>
|
||||
<release version="3.8.0" date="2019-05-30" description="Hippo">
|
||||
<action type="fix">
|
||||
|
|
Loading…
Reference in New Issue