Property document OperationDefinitions generated automatically by server

This commit is contained in:
jamesagnew 2015-07-19 11:10:46 -04:00
parent 61cb60b293
commit a774a654ce
10 changed files with 170 additions and 22 deletions

View File

@ -27,6 +27,9 @@ import java.lang.annotation.Target;
import org.hl7.fhir.instance.model.api.IBase;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.param.StringParam;
/**
*/
@Retention(RetentionPolicy.RUNTIME)
@ -45,7 +48,13 @@ public @interface OperationParam {
/**
* The type of the parameter. This will only have effect on <code>@OperationParam</code>
* annotations specified as values for {@link Operation#returnParameters()}
* annotations specified as values for {@link Operation#returnParameters()}, otherwise the
* value will be ignored. Value should be one of:
* <ul>
* <li>A resource type, e.g. <code>Patient.class</code></li>
* <li>A datatype, e.g. <code>{@link StringDt}.class</code> or </code>CodeableConceptDt.class</code>
* <li>A RESTful search parameter type, e.g. <code>{@link StringParam}.class</code>
* </ul>
*/
Class<? extends IBase> type() default IBase.class;

View File

@ -1,7 +1,6 @@
package ca.uhn.fhir.rest.method;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.*;
import java.io.IOException;
import java.io.PushbackReader;
@ -457,12 +456,12 @@ public class MethodUtil {
param = new ConditionalParamBinder(theRestfulOperationTypeEnum);
} else if (nextAnnotation instanceof OperationParam) {
Operation op = theMethod.getAnnotation(Operation.class);
param = new OperationParameter(op.name(), ((OperationParam) nextAnnotation));
param = new OperationParameter(theContext, op.name(), ((OperationParam) nextAnnotation));
} else if (nextAnnotation instanceof Validate.Mode) {
if (parameterType.equals(ValidationModeEnum.class) == false) {
throw new ConfigurationException("Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Mode.class.getSimpleName() + " must be of type " + ValidationModeEnum.class.getName());
}
param = new OperationParameter(Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_MODE, 0, 1).setConverter(new IConverter() {
param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_MODE, 0, 1).setConverter(new IConverter() {
@Override
public Object incomingServer(Object theObject) {
return ValidationModeEnum.valueOf(theObject.toString().toUpperCase());
@ -477,7 +476,7 @@ public class MethodUtil {
if (parameterType.equals(String.class) == false) {
throw new ConfigurationException("Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Profile.class.getSimpleName() + " must be of type " + String.class.getName());
}
param = new OperationParameter(Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_PROFILE, 0, 1).setConverter(new IConverter() {
param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_PROFILE, 0, 1).setConverter(new IConverter() {
@Override
public Object incomingServer(Object theObject) {
return theObject.toString();

View File

@ -25,6 +25,7 @@ import static org.apache.commons.lang3.StringUtils.*;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -45,6 +46,7 @@ import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.server.IBundleProvider;
@ -61,9 +63,11 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
private final Integer myIdParamIndex;
private final String myName;
private final OtherOperationTypeEnum myOtherOperatiopnType;
private List<ReturnType> myReturnParams;
private final ReturnTypeEnum myReturnType;
public OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider, boolean theIdempotent, String theOperationName, Class<? extends IBaseResource> theOperationType) {
public OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider, boolean theIdempotent, String theOperationName, Class<? extends IBaseResource> theOperationType,
OperationParam[] theReturnParams) {
super(theReturnResourceType, theMethod, theContext, theProvider);
myIdempotent = theIdempotent;
@ -118,10 +122,27 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
} else {
myOtherOperatiopnType = OtherOperationTypeEnum.EXTENDED_OPERATION_INSTANCE;
}
myReturnParams = new ArrayList<OperationMethodBinding.ReturnType>();
if (theReturnParams != null) {
for (OperationParam next : theReturnParams) {
ReturnType type = new ReturnType();
type.setName(next.name());
type.setMin(next.min());
type.setMax(next.max());
if (!next.type().equals(IBase.class)) {
if (next.type().isInterface() || Modifier.isAbstract(next.type().getModifiers())) {
throw new ConfigurationException("Invalid value for @OperationParam.type(): " + next.type().getName());
}
type.setType(theContext.getElementDefinition(next.type()).getName());
}
myReturnParams.add(type);
}
}
}
public OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider, Operation theAnnotation) {
this(theReturnResourceType, theReturnTypeFromRp, theMethod, theContext, theProvider, theAnnotation.idempotent(), theAnnotation.name(), theAnnotation.type());
this(theReturnResourceType, theReturnTypeFromRp, theMethod, theContext, theProvider, theAnnotation.idempotent(), theAnnotation.name(), theAnnotation.type(), theAnnotation.returnParameters());
}
public String getDescription() {
@ -150,6 +171,10 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
return null;
}
public List<ReturnType> getReturnParams() {
return Collections.unmodifiableList(myReturnParams);
}
@Override
public ReturnTypeEnum getReturnType() {
return myReturnType;
@ -281,4 +306,49 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
return new HttpGetClientInvocation(params, b.toString());
}
}
public static class ReturnType {
private int myMax;
private int myMin;
private String myName;
/**
* http://hl7-fhir.github.io/valueset-operation-parameter-type.html
*/
private String myType;
public int getMax() {
return myMax;
}
public int getMin() {
return myMin;
}
public String getName() {
return myName;
}
public String getType() {
return myType;
}
public void setMax(int theMax) {
myMax = theMax;
}
public void setMin(int theMin) {
myMin = theMin;
}
public void setName(String theName) {
myName = theName;
}
public void setType(String theType) {
myType = theType;
}
}
}

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.method;
*/
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@ -33,6 +34,7 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition.IAccessor;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeChildPrimitiveDatatypeDefinition;
import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition;
@ -57,17 +59,19 @@ public class OperationParameter implements IParameter {
private final String myName;
private final String myOperationName;
private Class<?> myParameterType;
private RestSearchParameterTypeEnum myParamType;
private String myParamType;
private FhirContext myContext;
public OperationParameter(String theOperationName, OperationParam theOperationParam) {
this(theOperationName, theOperationParam.name(), theOperationParam.min(), theOperationParam.max());
public OperationParameter(FhirContext theCtx, String theOperationName, OperationParam theOperationParam) {
this(theCtx, theOperationName, theOperationParam.name(), theOperationParam.min(), theOperationParam.max());
}
OperationParameter(String theOperationName, String theParameterName, int theMin, int theMax) {
OperationParameter(FhirContext theCtx, String theOperationName, String theParameterName, int theMin, int theMax) {
myOperationName = theOperationName;
myName = theParameterName;
myMin = theMin;
myMax = theMax;
myContext = theCtx;
}
@ -84,10 +88,11 @@ public class OperationParameter implements IParameter {
return myName;
}
public RestSearchParameterTypeEnum getParamType() {
public String getParamType() {
return myParamType;
}
@SuppressWarnings("unchecked")
@Override
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
myParameterType = theParameterType;
@ -96,6 +101,14 @@ public class OperationParameter implements IParameter {
} else {
myMax = 1;
}
if (!myParameterType.equals(IBase.class)) {
if (!IBase.class.isAssignableFrom(myParameterType) || myParameterType.isInterface() || Modifier.isAbstract(myParameterType.getModifiers())) {
throw new ConfigurationException("Invalid type for @OperationParam: " + myParameterType.getName());
}
myParamType = myContext.getElementDefinition((Class<? extends IBase>) myParameterType).getName();
}
}
public OperationParameter setConverter(IConverter theConverter) {

View File

@ -28,6 +28,7 @@ import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.param.ResourceParameter;
@ -39,7 +40,7 @@ public class ValidateMethodBindingDstu2 extends OperationMethodBinding {
public ValidateMethodBindingDstu2(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
Validate theAnnotation) {
super(theReturnResourceType, theReturnTypeFromRp, theMethod, theContext, theProvider, true, Constants.EXTOP_VALIDATE, theAnnotation.type());
super(theReturnResourceType, theReturnTypeFromRp, theMethod, theContext, theProvider, true, Constants.EXTOP_VALIDATE, theAnnotation.type(), new OperationParam[0]);
List<IParameter> newParams = new ArrayList<IParameter>();
int idx = 0;
@ -50,7 +51,7 @@ public class ValidateMethodBindingDstu2 extends OperationMethodBinding {
if (String.class.equals(parameterType) || EncodingEnum.class.equals(parameterType)) {
newParams.add(next);
} else {
OperationParameter parameter = new OperationParameter(Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_RESOURCE, 0, 1);
OperationParameter parameter = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_RESOURCE, 0, 1);
parameter.initializeTypes(theMethod, null, null, parameterType);
newParams.add(parameter);
}

View File

@ -63,6 +63,7 @@ import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.DynamicSearchMethodBinding;
import ca.uhn.fhir.rest.method.IParameter;
import ca.uhn.fhir.rest.method.OperationMethodBinding;
import ca.uhn.fhir.rest.method.OperationMethodBinding.ReturnType;
import ca.uhn.fhir.rest.method.OperationParameter;
import ca.uhn.fhir.rest.method.SearchMethodBinding;
import ca.uhn.fhir.rest.method.SearchParameter;
@ -161,7 +162,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
Parameter param = op.addParameter();
param.setUse(OperationParameterUseEnum.IN);
if (nextParam.getParamType() != null) {
param.setType(nextParam.getParamType().getCode());
param.setType(nextParam.getParamType());
}
param.setMin(nextParam.getMin());
param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax()));
@ -169,6 +170,18 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
}
}
for (ReturnType nextParam : methodBinding.getReturnParams()) {
Parameter param = op.addParameter();
param.setUse(OperationParameterUseEnum.OUT);
if (nextParam.getType() != null) {
param.setType(nextParam.getType());
}
param.setMin(nextParam.getMin());
param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax()));
param.setName(nextParam.getName());
}
return op;
}

View File

@ -269,7 +269,18 @@ public class ServerConformanceProviderDstu2Test {
assertEquals("$plain", opDef.getCode());
assertEquals(true, opDef.getIdempotent().booleanValue());
}
assertEquals(3, opDef.getParameter().size());
assertEquals("start", opDef.getParameter().get(0).getName());
assertEquals("in", opDef.getParameter().get(0).getUse());
assertEquals("0", opDef.getParameter().get(0).getMinElement().getValueAsString());
assertEquals("date", opDef.getParameter().get(0).getTypeElement().getValueAsString());
assertEquals("out1", opDef.getParameter().get(2).getName());
assertEquals("out", opDef.getParameter().get(2).getUse());
assertEquals("1", opDef.getParameter().get(2).getMinElement().getValueAsString());
assertEquals("2", opDef.getParameter().get(2).getMaxElement().getValueAsString());
assertEquals("string", opDef.getParameter().get(2).getTypeElement().getValueAsString());
}
@Test
public void testProviderWithRequiredAndOptional() throws Exception {
@ -503,7 +514,9 @@ public class ServerConformanceProviderDstu2Test {
public static class PlainProviderWithExtendedOperationOnNoType {
@Operation(name = "plain", idempotent = true)
@Operation(name = "plain", idempotent = true, returnParameters= {
@OperationParam(min=1, max=2, name="out1", type=StringDt.class)
})
public ca.uhn.fhir.rest.server.IBundleProvider everything(javax.servlet.http.HttpServletRequest theServletRequest, @IdParam ca.uhn.fhir.model.primitive.IdDt theId, @OperationParam(name = "start") DateDt theStart, @OperationParam(name = "end") DateDt theEnd) {
return null;
}

View File

@ -57,11 +57,13 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Initialize;
import ca.uhn.fhir.rest.annotation.Metadata;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.DynamicSearchMethodBinding;
import ca.uhn.fhir.rest.method.IParameter;
import ca.uhn.fhir.rest.method.OperationMethodBinding;
import ca.uhn.fhir.rest.method.OperationMethodBinding.ReturnType;
import ca.uhn.fhir.rest.method.OperationParameter;
import ca.uhn.fhir.rest.method.SearchMethodBinding;
import ca.uhn.fhir.rest.method.SearchParameter;
@ -159,13 +161,24 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
OperationDefinitionParameterComponent param = op.addParameter();
param.setUse(OperationParameterUse.IN);
if (nextParam.getParamType() != null) {
param.setType(nextParam.getParamType().getCode());
param.setType(nextParam.getParamType());
}
param.setMin(nextParam.getMin());
param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax()));
param.setName(nextParam.getName());
}
}
for (ReturnType nextParam : methodBinding.getReturnParams()) {
OperationDefinitionParameterComponent param = op.addParameter();
param.setUse(OperationParameterUse.OUT);
if (nextParam.getType() != null) {
param.setType(nextParam.getType());
}
param.setMin(nextParam.getMin());
param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax()));
param.setName(nextParam.getName());
}
return op;
}

View File

@ -261,7 +261,7 @@ public class ServerConformanceProviderHl7OrgDstu2Test {
Conformance sconf = sc.getServerConformance(createHttpServletRequest());
assertEquals("OperationDefinition/plain", sconf.getRest().get(0).getOperation().get(0).getDefinition().getReference());
OperationDefinition opDef = sc.readOperationDefinition(new IdDt("OperationDefinition/plain"));
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef);
@ -269,7 +269,18 @@ public class ServerConformanceProviderHl7OrgDstu2Test {
assertEquals("$plain", opDef.getCode());
assertEquals(true, opDef.getIdempotent());
}
assertEquals(3, opDef.getParameter().size());
assertEquals("start", opDef.getParameter().get(0).getName());
assertEquals("in", opDef.getParameter().get(0).getUse().toCode());
assertEquals("0", opDef.getParameter().get(0).getMinElement().getValueAsString());
assertEquals("date", opDef.getParameter().get(0).getTypeElement().getValueAsString());
assertEquals("out1", opDef.getParameter().get(2).getName());
assertEquals("out", opDef.getParameter().get(2).getUse().toCode());
assertEquals("1", opDef.getParameter().get(2).getMinElement().getValueAsString());
assertEquals("2", opDef.getParameter().get(2).getMaxElement().getValueAsString());
assertEquals("string", opDef.getParameter().get(2).getTypeElement().getValueAsString());
}
@Test
public void testProviderWithRequiredAndOptional() throws Exception {
@ -503,7 +514,9 @@ public class ServerConformanceProviderHl7OrgDstu2Test {
public static class PlainProviderWithExtendedOperationOnNoType {
@Operation(name = "plain", idempotent = true)
@Operation(name = "plain", idempotent = true, returnParameters= {
@OperationParam(min=1, max=2, name="out1", type=StringDt.class)
})
public ca.uhn.fhir.rest.server.IBundleProvider everything(javax.servlet.http.HttpServletRequest theServletRequest, @IdParam ca.uhn.fhir.model.primitive.IdDt theId, @OperationParam(name = "start") DateDt theStart, @OperationParam(name = "end") DateDt theEnd) {
return null;
}

View File

@ -41,6 +41,10 @@
XmlParser and JsonParser in DSTU2 mode should not encode empty
tags in resource. Thanks to Bill De Beaubien for reporting!
</action>
<action>
OperationDefinitions generated by server did not properly document
their return parameters or the type of their input parameters.
</action>
</release>
<release version="1.1" date="2015-07-13">
<action type="add">