Issue 430: Generate OperationDefinitions for named queries, fix minor errors in generation of OperationDefinition for operations. Closes #430.

This commit is contained in:
Stig Rohde Døssing 2018-08-23 13:14:01 +02:00 committed by James Agnew
parent 62ae71c1c6
commit f8b232bb67
5 changed files with 328 additions and 55 deletions

View File

@ -450,8 +450,7 @@ public abstract class BaseMethodBinding<T> {
if (returnTypeFromRp != null) { if (returnTypeFromRp != null) {
if (returnTypeFromAnnotation != null && !isResourceInterface(returnTypeFromAnnotation)) { if (returnTypeFromAnnotation != null && !isResourceInterface(returnTypeFromAnnotation)) {
if (!returnTypeFromRp.isAssignableFrom(returnTypeFromAnnotation)) { if (returnTypeFromMethod != null && !returnTypeFromRp.isAssignableFrom(returnTypeFromMethod)) {
//FIXME potential null access on retunrTypeFromMethod
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " returns type " throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " returns type "
+ returnTypeFromMethod.getCanonicalName() + " - Must return " + returnTypeFromRp.getCanonicalName() + " (or a subclass of it) per IResourceProvider contract"); + returnTypeFromMethod.getCanonicalName() + " - Must return " + returnTypeFromRp.getCanonicalName() + " (or a subclass of it) per IResourceProvider contract");
} }
@ -479,7 +478,7 @@ public abstract class BaseMethodBinding<T> {
if (read != null) { if (read != null) {
return new ReadMethodBinding(returnType, theMethod, theContext, theProvider); return new ReadMethodBinding(returnType, theMethod, theContext, theProvider);
} else if (search != null) { } else if (search != null) {
return new SearchMethodBinding(returnType, theMethod, theContext, theProvider); return new SearchMethodBinding(returnType, returnTypeFromRp, theMethod, theContext, theProvider);
} else if (conformance != null) { } else if (conformance != null) {
return new ConformanceMethodBinding(theMethod, theContext, theProvider); return new ConformanceMethodBinding(theMethod, theContext, theProvider);
} else if (create != null) { } else if (create != null) {

View File

@ -59,7 +59,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
private final boolean myIdempotent; private final boolean myIdempotent;
private final Integer myIdParamIndex; private final Integer myIdParamIndex;
private final String myName; private final String myName;
private final RestOperationTypeEnum myOtherOperatiopnType; private final RestOperationTypeEnum myOtherOperationType;
private final ReturnTypeEnum myReturnType; private final ReturnTypeEnum myReturnType;
private BundleTypeEnum myBundleType; private BundleTypeEnum myBundleType;
private boolean myCanOperateAtInstanceLevel; private boolean myCanOperateAtInstanceLevel;
@ -75,16 +75,6 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
myBundleType = theBundleType; myBundleType = theBundleType;
myIdempotent = theIdempotent; myIdempotent = theIdempotent;
myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, getContext());
if (myIdParamIndex != null) {
for (Annotation next : theMethod.getParameterAnnotations()[myIdParamIndex]) {
if (next instanceof IdParam) {
myCanOperateAtTypeLevel = ((IdParam) next).optional() == true;
}
}
} else {
myCanOperateAtTypeLevel = true;
}
Description description = theMethod.getAnnotation(Description.class); Description description = theMethod.getAnnotation(Description.class);
if (description != null) { if (description != null) {
@ -108,13 +98,11 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
if (theReturnTypeFromRp != null) { if (theReturnTypeFromRp != null) {
setResourceName(theContext.getResourceDefinition(theReturnTypeFromRp).getName()); setResourceName(theContext.getResourceDefinition(theReturnTypeFromRp).getName());
} else { } else if (Modifier.isAbstract(theOperationType.getModifiers()) == false) {
if (Modifier.isAbstract(theOperationType.getModifiers()) == false) {
setResourceName(theContext.getResourceDefinition(theOperationType).getName()); setResourceName(theContext.getResourceDefinition(theOperationType).getName());
} else { } else {
setResourceName(null); setResourceName(null);
} }
}
if (theMethod.getReturnType().equals(IBundleProvider.class)) { if (theMethod.getReturnType().equals(IBundleProvider.class)) {
myReturnType = ReturnTypeEnum.BUNDLE; myReturnType = ReturnTypeEnum.BUNDLE;
@ -122,12 +110,24 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
myReturnType = ReturnTypeEnum.RESOURCE; myReturnType = ReturnTypeEnum.RESOURCE;
} }
myIdParamIndex = ParameterUtil.findIdParameterIndex(theMethod, getContext());
if (getResourceName() == null) { if (getResourceName() == null) {
myOtherOperatiopnType = RestOperationTypeEnum.EXTENDED_OPERATION_SERVER; myOtherOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_SERVER;
myCanOperateAtServerLevel = true;
if (myIdParamIndex != null) {
myCanOperateAtInstanceLevel = true;
}
} else if (myIdParamIndex == null) { } else if (myIdParamIndex == null) {
myOtherOperatiopnType = RestOperationTypeEnum.EXTENDED_OPERATION_TYPE; myOtherOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_TYPE;
myCanOperateAtTypeLevel = true;
} else { } else {
myOtherOperatiopnType = RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE; myOtherOperationType = RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE;
myCanOperateAtInstanceLevel = true;
for (Annotation next : theMethod.getParameterAnnotations()[myIdParamIndex]) {
if (next instanceof IdParam) {
myCanOperateAtTypeLevel = ((IdParam) next).optional() == true;
}
}
} }
myReturnParams = new ArrayList<>(); myReturnParams = new ArrayList<>();
@ -149,14 +149,6 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
myReturnParams.add(type); myReturnParams.add(type);
} }
} }
if (myIdParamIndex != null) {
myCanOperateAtInstanceLevel = true;
}
if (getResourceName() == null) {
myCanOperateAtServerLevel = true;
}
} }
public OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider, public OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
@ -188,7 +180,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
@Nonnull @Nonnull
@Override @Override
public RestOperationTypeEnum getRestOperationType() { public RestOperationTypeEnum getRestOperationType() {
return myOtherOperatiopnType; return myOtherOperationType;
} }
public List<ReturnType> getReturnParams() { public List<ReturnType> getReturnParams() {
@ -230,16 +222,12 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
boolean requestHasId = theRequest.getId() != null; boolean requestHasId = theRequest.getId() != null;
if (requestHasId) { if (requestHasId) {
if (isCanOperateAtInstanceLevel() == false) { return myCanOperateAtInstanceLevel;
return false;
} }
} else { if (isNotBlank(theRequest.getResourceName())) {
if (myCanOperateAtTypeLevel == false) { return myCanOperateAtTypeLevel;
return false;
} }
} return myCanOperateAtServerLevel;
return true;
} }
@Override @Override

View File

@ -56,6 +56,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
private Integer myIdParamIndex; private Integer myIdParamIndex;
private String myQueryName; private String myQueryName;
private boolean myAllowUnknownParams; private boolean myAllowUnknownParams;
private final String myResourceProviderResourceName;
static { static {
HashSet<String> specialSearchParams = new HashSet<>(); HashSet<String> specialSearchParams = new HashSet<>();
@ -64,7 +65,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
SPECIAL_SEARCH_PARAMS = Collections.unmodifiableSet(specialSearchParams); SPECIAL_SEARCH_PARAMS = Collections.unmodifiableSet(specialSearchParams);
} }
public SearchMethodBinding(Class<? extends IBaseResource> theReturnResourceType, Method theMethod, FhirContext theContext, Object theProvider) { public SearchMethodBinding(Class<? extends IBaseResource> theReturnResourceType, Class<? extends IBaseResource> theResourceProviderResourceType, Method theMethod, FhirContext theContext, Object theProvider) {
super(theReturnResourceType, theMethod, theContext, theProvider); super(theReturnResourceType, theMethod, theContext, theProvider);
Search search = theMethod.getAnnotation(Search.class); Search search = theMethod.getAnnotation(Search.class);
this.myQueryName = StringUtils.defaultIfBlank(search.queryName(), null); this.myQueryName = StringUtils.defaultIfBlank(search.queryName(), null);
@ -89,12 +90,26 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
throw new ConfigurationException(msg); throw new ConfigurationException(msg);
} }
if (theResourceProviderResourceType != null) {
this.myResourceProviderResourceName = theContext.getResourceDefinition(theResourceProviderResourceType).getName();
} else {
this.myResourceProviderResourceName = null;
}
} }
public String getDescription() { public String getDescription() {
return myDescription; return myDescription;
} }
public String getQueryName() {
return myQueryName;
}
public String getResourceProviderResourceName() {
return myResourceProviderResourceName;
}
@Nonnull @Nonnull
@Override @Override
public RestOperationTypeEnum getRestOperationType() { public RestOperationTypeEnum getRestOperationType() {

View File

@ -71,6 +71,8 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerCapabilityStatementProvider.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerCapabilityStatementProvider.class);
private boolean myCache = true; private boolean myCache = true;
private volatile CapabilityStatement myCapabilityStatement; private volatile CapabilityStatement myCapabilityStatement;
private IdentityHashMap<SearchMethodBinding, String> myNamedSearchMethodBindingToName;
private HashMap<String, List<SearchMethodBinding>> mySearchNameToBindings;
private IdentityHashMap<OperationMethodBinding, String> myOperationBindingToName; private IdentityHashMap<OperationMethodBinding, String> myOperationBindingToName;
private HashMap<String, List<OperationMethodBinding>> myOperationNameToBindings; private HashMap<String, List<OperationMethodBinding>> myOperationNameToBindings;
private String myPublisher = "Not provided"; private String myPublisher = "Not provided";
@ -151,6 +153,17 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
return DateTimeType.now(); return DateTimeType.now();
} }
private String createNamedQueryName(SearchMethodBinding searchMethodBinding) {
StringBuilder retVal = new StringBuilder();
if (searchMethodBinding.getResourceName() != null) {
retVal.append(searchMethodBinding.getResourceName());
}
retVal.append("-query-");
retVal.append(searchMethodBinding.getQueryName());
return retVal.toString();
}
private String createOperationName(OperationMethodBinding theMethodBinding) { private String createOperationName(OperationMethodBinding theMethodBinding) {
StringBuilder retVal = new StringBuilder(); StringBuilder retVal = new StringBuilder();
if (theMethodBinding.getResourceName() != null) { if (theMethodBinding.getResourceName() != null) {
@ -297,7 +310,15 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
checkBindingForSystemOps(rest, systemOps, nextMethodBinding); checkBindingForSystemOps(rest, systemOps, nextMethodBinding);
if (nextMethodBinding instanceof SearchMethodBinding) { if (nextMethodBinding instanceof SearchMethodBinding) {
handleSearchMethodBinding(rest, resource, resourceName, def, includes, (SearchMethodBinding) nextMethodBinding); SearchMethodBinding methodBinding = (SearchMethodBinding) nextMethodBinding;
if (methodBinding.getQueryName() != null) {
String queryName = myNamedSearchMethodBindingToName.get(methodBinding);
if (operationNames.add(queryName)) {
rest.addOperation().setName(methodBinding.getQueryName()).setDefinition(new Reference("OperationDefinition/" + queryName));
}
} else {
handleNamelessSearchMethodBinding(rest, resource, resourceName, def, includes, (SearchMethodBinding) nextMethodBinding);
}
} else if (nextMethodBinding instanceof OperationMethodBinding) { } else if (nextMethodBinding instanceof OperationMethodBinding) {
OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding; OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
String opName = myOperationBindingToName.get(methodBinding); String opName = myOperationBindingToName.get(methodBinding);
@ -349,7 +370,7 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
return retVal; return retVal;
} }
private void handleSearchMethodBinding(CapabilityStatementRestComponent rest, CapabilityStatementRestResourceComponent resource, String resourceName, RuntimeResourceDefinition def, TreeSet<String> includes, private void handleNamelessSearchMethodBinding(CapabilityStatementRestComponent rest, CapabilityStatementRestResourceComponent resource, String resourceName, RuntimeResourceDefinition def, TreeSet<String> includes,
SearchMethodBinding searchMethodBinding) { SearchMethodBinding searchMethodBinding) {
includes.addAll(searchMethodBinding.getIncludes()); includes.addAll(searchMethodBinding.getIncludes());
@ -438,8 +459,10 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
@Initialize @Initialize
public void initializeOperations() { public void initializeOperations() {
myOperationBindingToName = new IdentityHashMap<OperationMethodBinding, String>(); myNamedSearchMethodBindingToName = new IdentityHashMap<>();
myOperationNameToBindings = new HashMap<String, List<OperationMethodBinding>>(); mySearchNameToBindings = new HashMap<>();
myOperationBindingToName = new IdentityHashMap<>();
myOperationNameToBindings = new HashMap<>();
Map<String, List<BaseMethodBinding<?>>> resourceToMethods = collectMethodBindings(); Map<String, List<BaseMethodBinding<?>>> resourceToMethods = collectMethodBindings();
for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) { for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) {
@ -456,9 +479,23 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
myOperationBindingToName.put(methodBinding, name); myOperationBindingToName.put(methodBinding, name);
if (myOperationNameToBindings.containsKey(name) == false) { if (myOperationNameToBindings.containsKey(name) == false) {
myOperationNameToBindings.put(name, new ArrayList<OperationMethodBinding>()); myOperationNameToBindings.put(name, new ArrayList<>());
} }
myOperationNameToBindings.get(name).add(methodBinding); myOperationNameToBindings.get(name).add(methodBinding);
} else if (nextMethodBinding instanceof SearchMethodBinding) {
SearchMethodBinding methodBinding = (SearchMethodBinding) nextMethodBinding;
if (myNamedSearchMethodBindingToName.containsKey(methodBinding)) {
continue;
}
String name = createNamedQueryName(methodBinding);
ourLog.debug("Detected named query: {}", name);
myNamedSearchMethodBindingToName.put(methodBinding, name);
if (!mySearchNameToBindings.containsKey(name)) {
mySearchNameToBindings.put(name, new ArrayList<>());
}
mySearchNameToBindings.get(name).add(methodBinding);
} }
} }
} }
@ -469,11 +506,69 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
if (theId == null || theId.hasIdPart() == false) { if (theId == null || theId.hasIdPart() == false) {
throw new ResourceNotFoundException(theId); throw new ResourceNotFoundException(theId);
} }
List<OperationMethodBinding> sharedDescriptions = myOperationNameToBindings.get(theId.getIdPart()); List<OperationMethodBinding> operationBindings = myOperationNameToBindings.get(theId.getIdPart());
if (sharedDescriptions == null || sharedDescriptions.isEmpty()) { if (operationBindings != null && !operationBindings.isEmpty()) {
return readOperationDefinitionForOperation(operationBindings);
}
List<SearchMethodBinding> searchBindings = mySearchNameToBindings.get(theId.getIdPart());
if (searchBindings != null && !searchBindings.isEmpty()) {
return readOperationDefinitionForNamedSearch(searchBindings);
}
throw new ResourceNotFoundException(theId); throw new ResourceNotFoundException(theId);
} }
private OperationDefinition readOperationDefinitionForNamedSearch(List<SearchMethodBinding> bindings) {
OperationDefinition op = new OperationDefinition();
op.setStatus(PublicationStatus.ACTIVE);
op.setKind(OperationKind.QUERY);
op.setIdempotent(true);
op.setSystem(false);
op.setType(false);
op.setInstance(false);
Set<String> inParams = new HashSet<>();
for (SearchMethodBinding binding : bindings) {
if (isNotBlank(binding.getDescription())) {
op.setDescription(binding.getDescription());
}
if (isBlank(binding.getResourceProviderResourceName())) {
op.setSystem(true);
} else {
op.setType(true);
op.addResourceElement().setValue(binding.getResourceProviderResourceName());
}
op.setCode(binding.getQueryName());
for (IParameter nextParamUntyped : binding.getParameters()) {
if (nextParamUntyped instanceof SearchParameter) {
SearchParameter nextParam = (SearchParameter) nextParamUntyped;
if (!inParams.add(nextParam.getName())) {
continue;
}
OperationDefinitionParameterComponent param = op.addParameter();
param.setUse(OperationParameterUse.IN);
param.setType("string");
param.getSearchTypeElement().setValueAsString(nextParam.getParamType().getCode());
param.setMin(nextParam.isRequired() ? 1 : 0);
param.setMax("1");
param.setName(nextParam.getName());
}
}
if (isBlank(op.getName())) {
if (isNotBlank(op.getDescription())) {
op.setName(op.getDescription());
} else {
op.setName(op.getCode());
}
}
}
return op;
}
private OperationDefinition readOperationDefinitionForOperation(List<OperationMethodBinding> bindings) {
OperationDefinition op = new OperationDefinition(); OperationDefinition op = new OperationDefinition();
op.setStatus(PublicationStatus.ACTIVE); op.setStatus(PublicationStatus.ACTIVE);
op.setKind(OperationKind.OPERATION); op.setKind(OperationKind.OPERATION);
@ -484,10 +579,10 @@ public class ServerCapabilityStatementProvider implements IServerConformanceProv
op.setType(false); op.setType(false);
op.setInstance(false); op.setInstance(false);
Set<String> inParams = new HashSet<String>(); Set<String> inParams = new HashSet<>();
Set<String> outParams = new HashSet<String>(); Set<String> outParams = new HashSet<>();
for (OperationMethodBinding sharedDescription : sharedDescriptions) { for (OperationMethodBinding sharedDescription : bindings) {
if (isNotBlank(sharedDescription.getDescription())) { if (isNotBlank(sharedDescription.getDescription())) {
op.setDescription(sharedDescription.getDescription()); op.setDescription(sharedDescription.getDescription());
} }

View File

@ -29,6 +29,7 @@ import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.rest.annotation.*; import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.*; import ca.uhn.fhir.rest.server.*;
@ -37,6 +38,14 @@ import ca.uhn.fhir.rest.server.method.SearchParameter;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult; import ca.uhn.fhir.validation.ValidationResult;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus;
import org.hl7.fhir.dstu3.model.OperationDefinition.OperationDefinitionParameterComponent;
import org.hl7.fhir.dstu3.model.OperationDefinition.OperationKind;
import org.hl7.fhir.dstu3.model.OperationDefinition.OperationParameterUse;
public class ServerCapabilityStatementProviderDstu3Test { public class ServerCapabilityStatementProviderDstu3Test {
@ -124,6 +133,9 @@ public class ServerCapabilityStatementProviderDstu3Test {
OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-everything")); OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-everything"));
validate(opDef); validate(opDef);
assertEquals("everything", opDef.getCode()); assertEquals("everything", opDef.getCode());
assertThat(opDef.getSystem(), is(false));
assertThat(opDef.getType(), is(false));
assertThat(opDef.getInstance(), is(true));
} }
@Test @Test
@ -344,6 +356,10 @@ public class ServerCapabilityStatementProviderDstu3Test {
assertEquals("1", opDef.getParameter().get(2).getMinElement().getValueAsString()); assertEquals("1", opDef.getParameter().get(2).getMinElement().getValueAsString());
assertEquals("2", opDef.getParameter().get(2).getMaxElement().getValueAsString()); assertEquals("2", opDef.getParameter().get(2).getMaxElement().getValueAsString());
assertEquals("string", opDef.getParameter().get(2).getTypeElement().getValueAsString()); assertEquals("string", opDef.getParameter().get(2).getTypeElement().getValueAsString());
assertThat(opDef.getSystem(), is(true));
assertThat(opDef.getType(), is(false));
assertThat(opDef.getInstance(), is(true));
} }
@Test @Test
@ -622,6 +638,120 @@ public class ServerCapabilityStatementProviderDstu3Test {
assertTrue(result.getMessages().toString(), result.isSuccessful()); assertTrue(result.getMessages().toString(), result.isSuccessful());
} }
@Test
public void testSystemLevelNamedQueryWithParameters() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
rs.setProviders(new NamedQueryPlainProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest());
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
CapabilityStatementRestComponent restComponent = conformance.getRest().get(0);
CapabilityStatementRestOperationComponent operationComponent = restComponent.getOperation().get(0);
assertThat(operationComponent.getName(), is(NamedQueryPlainProvider.QUERY_NAME));
String operationReference = operationComponent.getDefinition().getReference();
assertThat(operationReference, not(nullValue()));
OperationDefinition operationDefinition = sc.readOperationDefinition(new IdType(operationReference));
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationDefinition));
validate(operationDefinition);
assertThat(operationDefinition.getCode(), is(NamedQueryPlainProvider.QUERY_NAME));
assertThat("The operation name should be the description, if a description is set", operationDefinition.getName(), is(NamedQueryPlainProvider.DESCRIPTION));
assertThat(operationDefinition.getStatus(), is(PublicationStatus.ACTIVE));
assertThat(operationDefinition.getKind(), is(OperationKind.QUERY));
assertThat(operationDefinition.getDescription(), is(NamedQueryPlainProvider.DESCRIPTION));
assertThat(operationDefinition.getIdempotent(), is(true));
assertThat("A system level search has no target resources", operationDefinition.getResource(), is(empty()));
assertThat(operationDefinition.getSystem(), is(true));
assertThat(operationDefinition.getType(), is(false));
assertThat(operationDefinition.getInstance(), is(false));
List<OperationDefinitionParameterComponent> parameters = operationDefinition.getParameter();
assertThat(parameters.size(), is(1));
OperationDefinitionParameterComponent param = parameters.get(0);
assertThat(param.getName(), is(NamedQueryPlainProvider.SP_QUANTITY));
assertThat(param.getType(), is("string"));
assertThat(param.getSearchTypeElement().asStringValue(), is(RestSearchParameterTypeEnum.QUANTITY.getCode()));
assertThat(param.getMin(), is(1));
assertThat(param.getMax(), is("1"));
assertThat(param.getUse(), is(OperationParameterUse.IN));
}
@Test
public void testResourceLevelNamedQueryWithParameters() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
rs.setProviders(new NamedQueryResourceProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest());
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance));
CapabilityStatementRestComponent restComponent = conformance.getRest().get(0);
CapabilityStatementRestOperationComponent operationComponent = restComponent.getOperation().get(0);
String operationReference = operationComponent.getDefinition().getReference();
assertThat(operationReference, not(nullValue()));
OperationDefinition operationDefinition = sc.readOperationDefinition(new IdType(operationReference));
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationDefinition));
validate(operationDefinition);
assertThat("The operation name should be the code if no description is set", operationDefinition.getName(), is(NamedQueryResourceProvider.QUERY_NAME));
String patientResourceName = "Patient";
assertThat("A resource level search targets the resource of the provider it's defined in", operationDefinition.getResource().get(0).getValue(), is(patientResourceName));
assertThat(operationDefinition.getSystem(), is(false));
assertThat(operationDefinition.getType(), is(true));
assertThat(operationDefinition.getInstance(), is(false));
List<OperationDefinitionParameterComponent> parameters = operationDefinition.getParameter();
assertThat(parameters.size(), is(1));
OperationDefinitionParameterComponent param = parameters.get(0);
assertThat(param.getName(), is(NamedQueryResourceProvider.SP_PARAM));
assertThat(param.getType(), is("string"));
assertThat(param.getSearchTypeElement().asStringValue(), is(RestSearchParameterTypeEnum.STRING.getCode()));
assertThat(param.getMin(), is(0));
assertThat(param.getMax(), is("1"));
assertThat(param.getUse(), is(OperationParameterUse.IN));
CapabilityStatementRestResourceComponent patientResource = restComponent.getResource().stream()
.filter(r -> patientResourceName.equals(r.getType()))
.findAny().get();
assertThat("Named query parameters should not appear in the resource search params", patientResource.getSearchParam(), is(empty()));
}
@Test
public void testExtendedOperationAtTypeLevel() throws Exception {
RestfulServer rs = new RestfulServer(ourCtx);
rs.setProviders(new TypeLevelOperationProvider());
ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
CapabilityStatement conformance = sc.getServerConformance(createHttpServletRequest());
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
List<CapabilityStatementRestOperationComponent> operations = conformance.getRest().get(0).getOperation();
assertThat(operations.size(), is(1));
assertThat(operations.get(0).getName(), is(TypeLevelOperationProvider.OPERATION_NAME));
OperationDefinition opDef = sc.readOperationDefinition(new IdType(operations.get(0).getDefinition().getReference()));
validate(opDef);
assertEquals(TypeLevelOperationProvider.OPERATION_NAME, opDef.getCode());
assertThat(opDef.getSystem(), is(false));
assertThat(opDef.getType(), is(true));
assertThat(opDef.getInstance(), is(false));
}
private List<String> toOperationIdParts(List<CapabilityStatementRestOperationComponent> theOperation) { private List<String> toOperationIdParts(List<CapabilityStatementRestOperationComponent> theOperation) {
ArrayList<String> retVal = Lists.newArrayList(); ArrayList<String> retVal = Lists.newArrayList();
for (CapabilityStatementRestOperationComponent next : theOperation) { for (CapabilityStatementRestOperationComponent next : theOperation) {
@ -934,4 +1064,50 @@ public class ServerCapabilityStatementProviderDstu3Test {
} }
public static class TypeLevelOperationProvider implements IResourceProvider {
public static final String OPERATION_NAME = "op";
@Operation(name = OPERATION_NAME, idempotent = true)
public IBundleProvider op() {
return null;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return Patient.class;
}
}
public static class NamedQueryPlainProvider {
public static final String QUERY_NAME = "testQuery";
public static final String DESCRIPTION = "A query description";
public static final String SP_QUANTITY = "quantity";
@Search(queryName = QUERY_NAME)
@Description(formalDefinition = DESCRIPTION)
public Bundle findAllGivenParameter(@RequiredParam(name = SP_QUANTITY) QuantityParam quantity) {
return null;
}
}
public static class NamedQueryResourceProvider implements IResourceProvider {
public static final String QUERY_NAME = "testQuery";
public static final String SP_PARAM = "param";
@Override
public Class<? extends IBaseResource> getResourceType() {
return Patient.class;
}
@Search(queryName = QUERY_NAME)
public Bundle findAllGivenParameter(@OptionalParam(name = SP_PARAM) StringParam param) {
return null;
}
}
} }