Complete Extended Operations support
This commit is contained in:
parent
06ea9a1453
commit
32ad3ab22c
|
@ -3,25 +3,20 @@ package example;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.omg.Dynamic.Parameter;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.Bundle;
|
import ca.uhn.fhir.model.api.Bundle;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.model.base.resource.BaseConformance;
|
import ca.uhn.fhir.model.base.resource.BaseConformance;
|
||||||
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
|
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
|
||||||
import ca.uhn.fhir.model.dstu2.valueset.AdministrativeGenderEnum;
|
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
|
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Organization;
|
import ca.uhn.fhir.model.dstu2.resource.Organization;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Parameters;
|
import ca.uhn.fhir.model.dstu2.resource.Parameters;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.ValueSet;
|
import ca.uhn.fhir.model.dstu2.valueset.AdministrativeGenderEnum;
|
||||||
import ca.uhn.fhir.model.primitive.CodeDt;
|
|
||||||
import ca.uhn.fhir.model.primitive.DateDt;
|
import ca.uhn.fhir.model.primitive.DateDt;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||||
import ca.uhn.fhir.model.primitive.UriDt;
|
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||||
|
@ -378,10 +373,12 @@ public class GenericClientExample {
|
||||||
IGenericClient client = ctx.newRestfulGenericClient("http://fhir-dev.healthintersections.com.au/open");
|
IGenericClient client = ctx.newRestfulGenericClient("http://fhir-dev.healthintersections.com.au/open");
|
||||||
client.registerInterceptor(new LoggingInterceptor(true));
|
client.registerInterceptor(new LoggingInterceptor(true));
|
||||||
|
|
||||||
|
// Create the input parameters to pass to the server
|
||||||
Parameters inParams = new Parameters();
|
Parameters inParams = new Parameters();
|
||||||
inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01"));
|
inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01"));
|
||||||
inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01"));
|
inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01"));
|
||||||
|
|
||||||
|
// Invoke $everything on "Patient/1"
|
||||||
Parameters outParams = client
|
Parameters outParams = client
|
||||||
.operation()
|
.operation()
|
||||||
.onInstance(new IdDt("Patient", "1"))
|
.onInstance(new IdDt("Patient", "1"))
|
||||||
|
|
|
@ -34,7 +34,7 @@ import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||||
import ca.uhn.fhir.parser.DataFormatException;
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
import ca.uhn.fhir.rest.annotation.AddTags;
|
import ca.uhn.fhir.rest.annotation.AddTags;
|
||||||
import ca.uhn.fhir.rest.annotation.ConditionalOperationParam;
|
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Count;
|
import ca.uhn.fhir.rest.annotation.Count;
|
||||||
import ca.uhn.fhir.rest.annotation.Create;
|
import ca.uhn.fhir.rest.annotation.Create;
|
||||||
import ca.uhn.fhir.rest.annotation.DeleteTags;
|
import ca.uhn.fhir.rest.annotation.DeleteTags;
|
||||||
|
@ -327,7 +327,7 @@ public void deletePatient(@IdParam IdDt theId) {
|
||||||
|
|
||||||
//START SNIPPET: deleteConditional
|
//START SNIPPET: deleteConditional
|
||||||
@Read()
|
@Read()
|
||||||
public void deletePatientConditional(@IdParam IdDt theId, @ConditionalOperationParam String theConditionalUrl) {
|
public void deletePatientConditional(@IdParam IdDt theId, @ConditionalUrlParam String theConditionalUrl) {
|
||||||
// Only one of theId or theConditionalUrl will have a value depending
|
// Only one of theId or theConditionalUrl will have a value depending
|
||||||
// on whether the URL receieved was a logical ID, or a conditional
|
// on whether the URL receieved was a logical ID, or a conditional
|
||||||
// search string
|
// search string
|
||||||
|
@ -698,7 +698,7 @@ public MethodOutcome createPatient(@ResourceParam Patient thePatient) {
|
||||||
@Create
|
@Create
|
||||||
public MethodOutcome createPatientConditional(
|
public MethodOutcome createPatientConditional(
|
||||||
@ResourceParam Patient thePatient,
|
@ResourceParam Patient thePatient,
|
||||||
@ConditionalOperationParam String theConditionalUrl) {
|
@ConditionalUrlParam String theConditionalUrl) {
|
||||||
|
|
||||||
if (theConditionalUrl != null) {
|
if (theConditionalUrl != null) {
|
||||||
// We are doing a conditional create
|
// We are doing a conditional create
|
||||||
|
@ -727,7 +727,7 @@ public abstract MethodOutcome createNewPatient(@ResourceParam Patient thePatient
|
||||||
public MethodOutcome updatePatientConditional(
|
public MethodOutcome updatePatientConditional(
|
||||||
@ResourceParam Patient thePatient,
|
@ResourceParam Patient thePatient,
|
||||||
@IdParam IdDt theId,
|
@IdParam IdDt theId,
|
||||||
@ConditionalOperationParam String theConditional) {
|
@ConditionalUrlParam String theConditional) {
|
||||||
|
|
||||||
// Only one of theId or theConditional will have a value and the other will be null,
|
// Only one of theId or theConditional will have a value and the other will be null,
|
||||||
// depending on the URL passed into the server.
|
// depending on the URL passed into the server.
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
package example;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.ConceptMap;
|
||||||
|
import ca.uhn.fhir.model.primitive.DateDt;
|
||||||
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Operation;
|
||||||
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
|
|
||||||
|
|
||||||
|
public class ServerOperations {
|
||||||
|
|
||||||
|
//START SNIPPET: patientTypeOperation
|
||||||
|
@Operation(name="$everything")
|
||||||
|
public Bundle patientTypeOperation(
|
||||||
|
@OperationParam(name="start") DateDt theStart,
|
||||||
|
@OperationParam(name="end") DateDt theEnd) {
|
||||||
|
|
||||||
|
Bundle retVal = new Bundle();
|
||||||
|
// Populate bundle with matching resources
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
//END SNIPPET: patientTypeOperation
|
||||||
|
|
||||||
|
//START SNIPPET: patientInstanceOperation
|
||||||
|
@Operation(name="$everything")
|
||||||
|
public Bundle patientInstanceOperation(
|
||||||
|
@IdParam IdDt thePatientId,
|
||||||
|
@OperationParam(name="start") DateDt theStart,
|
||||||
|
@OperationParam(name="end") DateDt theEnd) {
|
||||||
|
|
||||||
|
Bundle retVal = new Bundle();
|
||||||
|
// Populate bundle with matching resources
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
//END SNIPPET: patientInstanceOperation
|
||||||
|
|
||||||
|
//START SNIPPET: serverOperation
|
||||||
|
@Operation(name="$closure")
|
||||||
|
public ConceptMap closureOperation(
|
||||||
|
@OperationParam(name="name") StringDt theStart,
|
||||||
|
@OperationParam(name="concept") List<CodingDt> theEnd,
|
||||||
|
@OperationParam(name="version") IdDt theVersion) {
|
||||||
|
|
||||||
|
ConceptMap retVal = new ConceptMap();
|
||||||
|
// Populate bundle with matching resources
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
//END SNIPPET: serverOperation
|
||||||
|
|
||||||
|
}
|
|
@ -25,8 +25,15 @@ import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On the {@link Update}, {@link Create} and {@link Delete} operation methods, this annotation
|
||||||
|
* can be used to mark a {@link String} parameter which will be populated with the
|
||||||
|
* conditional "search" URL for the operation, if an incoming client invocation is
|
||||||
|
* a conditional operation. For non-conditional invocations, the value will be set to
|
||||||
|
* <code>null</code> so it is important to handle <code>null</code>.
|
||||||
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.PARAMETER)
|
@Target(ElementType.PARAMETER)
|
||||||
public @interface ConditionalOperationParam {
|
public @interface ConditionalUrlParam {
|
||||||
// just a marker
|
// just a marker
|
||||||
}
|
}
|
|
@ -25,13 +25,13 @@ import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import org.hl7.fhir.instance.model.IBaseResource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RESTful method annotation used for a method which provides
|
* RESTful method annotation used for a method which provides FHIR "operations".
|
||||||
* FHIR "operations".
|
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(value=ElementType.METHOD)
|
@Target(value = ElementType.METHOD)
|
||||||
public @interface Operation {
|
public @interface Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,4 +39,14 @@ public @interface Operation {
|
||||||
*/
|
*/
|
||||||
String name();
|
String name();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On a client, this value should be populated with the resource type that the operation applies to. If set to
|
||||||
|
* {@link IBaseResource} (which is the default) than the operation applies to the server and not to a specific
|
||||||
|
* resource type.
|
||||||
|
* <p>
|
||||||
|
* This value should not be populated on server implementations.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
Class<? extends IBaseResource> type() default IBaseResource.class;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(value=ElementType.PARAMETER)
|
||||||
|
public @interface OperationParam {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the parameter
|
||||||
|
*/
|
||||||
|
String name();
|
||||||
|
|
||||||
|
}
|
|
@ -153,7 +153,7 @@ abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding<Void>
|
||||||
}
|
}
|
||||||
for (int idx = 0; idx < theArgs.length; idx++) {
|
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||||
IParameter nextParam = getParameters().get(idx);
|
IParameter nextParam = getParameters().get(idx);
|
||||||
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null);
|
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return retVal;
|
return retVal;
|
||||||
|
|
|
@ -20,7 +20,7 @@ package ca.uhn.fhir.rest.method;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
import static org.apache.commons.lang3.StringUtils.*;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
|
@ -51,6 +51,7 @@ import ca.uhn.fhir.rest.annotation.DeleteTags;
|
||||||
import ca.uhn.fhir.rest.annotation.GetTags;
|
import ca.uhn.fhir.rest.annotation.GetTags;
|
||||||
import ca.uhn.fhir.rest.annotation.History;
|
import ca.uhn.fhir.rest.annotation.History;
|
||||||
import ca.uhn.fhir.rest.annotation.Metadata;
|
import ca.uhn.fhir.rest.annotation.Metadata;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Operation;
|
||||||
import ca.uhn.fhir.rest.annotation.Read;
|
import ca.uhn.fhir.rest.annotation.Read;
|
||||||
import ca.uhn.fhir.rest.annotation.Search;
|
import ca.uhn.fhir.rest.annotation.Search;
|
||||||
import ca.uhn.fhir.rest.annotation.Transaction;
|
import ca.uhn.fhir.rest.annotation.Transaction;
|
||||||
|
@ -257,9 +258,10 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
|
||||||
AddTags addTags = theMethod.getAnnotation(AddTags.class);
|
AddTags addTags = theMethod.getAnnotation(AddTags.class);
|
||||||
DeleteTags deleteTags = theMethod.getAnnotation(DeleteTags.class);
|
DeleteTags deleteTags = theMethod.getAnnotation(DeleteTags.class);
|
||||||
Transaction transaction = theMethod.getAnnotation(Transaction.class);
|
Transaction transaction = theMethod.getAnnotation(Transaction.class);
|
||||||
|
Operation operation = theMethod.getAnnotation(Operation.class);
|
||||||
|
|
||||||
// ** if you add another annotation above, also add it to the next line:
|
// ** if you add another annotation above, also add it to the next line:
|
||||||
if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance, create, update, delete, history, validate, getTags, addTags, deleteTags, transaction)) {
|
if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance, create, update, delete, history, validate, getTags, addTags, deleteTags, transaction, operation)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,7 +346,14 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
|
||||||
}
|
}
|
||||||
returnType = returnTypeFromAnnotation;
|
returnType = returnTypeFromAnnotation;
|
||||||
} else {
|
} else {
|
||||||
|
//if (IRestfulClient.class.isAssignableFrom(theMethod.getDeclaringClass())) {
|
||||||
|
// Clients don't define their methods in resource specific types, so they can
|
||||||
|
// infer their resource type from the method return type.
|
||||||
returnType = (Class<? extends IResource>) returnTypeFromMethod;
|
returnType = (Class<? extends IResource>) returnTypeFromMethod;
|
||||||
|
// } else {
|
||||||
|
// This is a plain provider method returning a resource, so it should be
|
||||||
|
// an operation or global search presumably
|
||||||
|
// returnType = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,6 +386,8 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
|
||||||
return new DeleteTagsMethodBinding(theMethod, theContext, theProvider, deleteTags);
|
return new DeleteTagsMethodBinding(theMethod, theContext, theProvider, deleteTags);
|
||||||
} else if (transaction != null) {
|
} else if (transaction != null) {
|
||||||
return new TransactionMethodBinding(theMethod, theContext, theProvider);
|
return new TransactionMethodBinding(theMethod, theContext, theProvider);
|
||||||
|
} else if (operation != null) {
|
||||||
|
return new OperationMethodBinding(returnType, returnTypeFromRp, theMethod, theContext, theProvider, operation);
|
||||||
} else {
|
} else {
|
||||||
throw new ConfigurationException("Did not detect any FHIR annotations on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
|
throw new ConfigurationException("Did not detect any FHIR annotations on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,8 +90,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
||||||
Class<?> collectionType = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(theMethod);
|
Class<?> collectionType = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(theMethod);
|
||||||
if (collectionType != null) {
|
if (collectionType != null) {
|
||||||
if (!Object.class.equals(collectionType) && !IResource.class.isAssignableFrom(collectionType)) {
|
if (!Object.class.equals(collectionType) && !IResource.class.isAssignableFrom(collectionType)) {
|
||||||
throw new ConfigurationException("Method " + theMethod.getDeclaringClass().getSimpleName() + "#" + theMethod.getName() + " returns an invalid collection generic type: "
|
throw new ConfigurationException("Method " + theMethod.getDeclaringClass().getSimpleName() + "#" + theMethod.getName() + " returns an invalid collection generic type: " + collectionType);
|
||||||
+ collectionType);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
myResourceListCollectionType = collectionType;
|
myResourceListCollectionType = collectionType;
|
||||||
|
@ -107,8 +106,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
||||||
} else if (IBundleProvider.class.isAssignableFrom(methodReturnType)) {
|
} else if (IBundleProvider.class.isAssignableFrom(methodReturnType)) {
|
||||||
myMethodReturnType = MethodReturnTypeEnum.BUNDLE_PROVIDER;
|
myMethodReturnType = MethodReturnTypeEnum.BUNDLE_PROVIDER;
|
||||||
} else {
|
} else {
|
||||||
throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: "
|
throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
|
||||||
+ theMethod.getDeclaringClass().getCanonicalName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theReturnResourceType != null) {
|
if (theReturnResourceType != null) {
|
||||||
|
@ -137,6 +135,11 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
||||||
return myResourceName;
|
return myResourceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the response is a bundle, this type will be placed in the root of the bundle (can be null)
|
||||||
|
*/
|
||||||
|
protected abstract BundleTypeEnum getResponseBundleType();
|
||||||
|
|
||||||
public abstract ReturnTypeEnum getReturnType();
|
public abstract ReturnTypeEnum getReturnType();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -261,9 +264,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We assume that the bundle we got back from the handling method
|
* We assume that the bundle we got back from the handling method may not have everything populated
|
||||||
* may not have everything populated (e.g. self links, bundle type,
|
* (e.g. self links, bundle type, etc) so we do that here.
|
||||||
* etc) so we do that here.
|
|
||||||
*/
|
*/
|
||||||
IVersionSpecificBundleFactory bundleFactory = theServer.getFhirContext().newBundleFactory();
|
IVersionSpecificBundleFactory bundleFactory = theServer.getFhirContext().newBundleFactory();
|
||||||
bundleFactory.initializeWithBundleResource(resource);
|
bundleFactory.initializeWithBundleResource(resource);
|
||||||
|
@ -275,8 +277,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
||||||
|
|
||||||
IBundleProvider result = (IBundleProvider) resultObj;
|
IBundleProvider result = (IBundleProvider) resultObj;
|
||||||
IVersionSpecificBundleFactory bundleFactory = theServer.getFhirContext().newBundleFactory();
|
IVersionSpecificBundleFactory bundleFactory = theServer.getFhirContext().newBundleFactory();
|
||||||
bundleFactory.initializeBundleFromBundleProvider(theServer, result, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, 0, count, null,
|
bundleFactory.initializeBundleFromBundleProvider(theServer, result, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, 0, count, null, getResponseBundleType());
|
||||||
getResponseBundleType());
|
|
||||||
Bundle bundle = bundleFactory.getDstu1Bundle();
|
Bundle bundle = bundleFactory.getDstu1Bundle();
|
||||||
if (bundle != null) {
|
if (bundle != null) {
|
||||||
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) {
|
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) {
|
||||||
|
@ -298,8 +299,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RestfulServerUtils.streamResponseAsResource(theServer, response, (IResource) resBundle, responseEncoding, prettyPrint, requestIsBrowser, narrativeMode,
|
RestfulServerUtils.streamResponseAsResource(theServer, response, (IResource) resBundle, responseEncoding, prettyPrint, requestIsBrowser, narrativeMode, Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), theRequest.getFhirServerBase());
|
||||||
Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), theRequest.getFhirServerBase());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -329,11 +329,6 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* If the response is a bundle, this type will be placed in the root of the bundle (can be null)
|
|
||||||
*/
|
|
||||||
protected abstract BundleTypeEnum getResponseBundleType();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subclasses may override
|
* Subclasses may override
|
||||||
*
|
*
|
||||||
|
@ -346,8 +341,12 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void setResourceName(String theResourceName) {
|
||||||
|
myResourceName = theResourceName;
|
||||||
|
}
|
||||||
|
|
||||||
public enum MethodReturnTypeEnum {
|
public enum MethodReturnTypeEnum {
|
||||||
BUNDLE, BUNDLE_PROVIDER, LIST_OF_RESOURCES, RESOURCE, BUNDLE_RESOURCE
|
BUNDLE, BUNDLE_PROVIDER, BUNDLE_RESOURCE, LIST_OF_RESOURCES, RESOURCE
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ReturnTypeEnum {
|
public enum ReturnTypeEnum {
|
||||||
|
|
|
@ -28,6 +28,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
|
import org.hl7.fhir.instance.model.IBaseResource;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
|
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
|
||||||
|
@ -45,7 +46,7 @@ class ConditionalParamBinder implements IParameter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
|
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding
|
||||||
if (theArgs != null) {
|
if (theArgs != null) {
|
||||||
for (int idx = 0; idx < theArgs.length; idx++) {
|
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||||
IParameter nextParam = getParameters().get(idx);
|
IParameter nextParam = getParameters().get(idx);
|
||||||
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null);
|
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.hl7.fhir.instance.model.IBaseResource;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
@ -43,7 +44,7 @@ public class CountParameter implements IParameter {
|
||||||
private Class<?> myType;
|
private Class<?> myType;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
|
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
|
||||||
if (theSourceClientArgument != null) {
|
if (theSourceClientArgument != null) {
|
||||||
IntegerDt since = ParameterUtil.toInteger(theSourceClientArgument);
|
IntegerDt since = ParameterUtil.toInteger(theSourceClientArgument);
|
||||||
if (since.isEmpty() == false) {
|
if (since.isEmpty() == false) {
|
||||||
|
|
|
@ -75,7 +75,7 @@ public class CreateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
|
||||||
if (theArgs != null) {
|
if (theArgs != null) {
|
||||||
for (int idx = 0; idx < theArgs.length; idx++) {
|
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||||
IParameter nextParam = getParameters().get(idx);
|
IParameter nextParam = getParameters().get(idx);
|
||||||
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null);
|
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -134,7 +134,7 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBinding {
|
||||||
|
|
||||||
for (int idx = 0; idx < theArgs.length; idx++) {
|
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||||
IParameter nextParam = getParameters().get(idx);
|
IParameter nextParam = getParameters().get(idx);
|
||||||
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null);
|
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return retVal;
|
return retVal;
|
||||||
|
|
|
@ -26,6 +26,8 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hl7.fhir.instance.model.IBaseResource;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
|
@ -58,7 +60,7 @@ public class DynamicSearchParameter implements IParameter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
|
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
|
||||||
throw new UnsupportedOperationException("Dynamic search is not supported in client mode (use fluent client for dynamic-like searches)");
|
throw new UnsupportedOperationException("Dynamic search is not supported in client mode (use fluent client for dynamic-like searches)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -140,7 +140,7 @@ public class GetTagsMethodBinding extends BaseMethodBinding<TagList> {
|
||||||
if (theArgs != null) {
|
if (theArgs != null) {
|
||||||
for (int idx = 0; idx < theArgs.length; idx++) {
|
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||||
IParameter nextParam = getParameters().get(idx);
|
IParameter nextParam = getParameters().get(idx);
|
||||||
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null);
|
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -157,7 +157,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
if (theArgs != null) {
|
if (theArgs != null) {
|
||||||
for (int idx = 0; idx < theArgs.length; idx++) {
|
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||||
IParameter nextParam = getParameters().get(idx);
|
IParameter nextParam = getParameters().get(idx);
|
||||||
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], retVal.getParameters());
|
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], retVal.getParameters(), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,8 @@ import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hl7.fhir.instance.model.IBaseResource;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||||
|
@ -33,7 +35,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
|
||||||
public interface IParameter {
|
public interface IParameter {
|
||||||
|
|
||||||
void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException;
|
void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This <b>server method</b> method takes the data received by the server in an incoming request, and translates that data into a single argument for a server method invocation. Note that all
|
* This <b>server method</b> method takes the data received by the server in an incoming request, and translates that data into a single argument for a server method invocation. Note that all
|
||||||
|
|
|
@ -48,10 +48,11 @@ import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||||
import ca.uhn.fhir.parser.IParser;
|
import ca.uhn.fhir.parser.IParser;
|
||||||
import ca.uhn.fhir.rest.annotation.ConditionalOperationParam;
|
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Count;
|
import ca.uhn.fhir.rest.annotation.Count;
|
||||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.IncludeParam;
|
import ca.uhn.fhir.rest.annotation.IncludeParam;
|
||||||
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
import ca.uhn.fhir.rest.annotation.OptionalParam;
|
import ca.uhn.fhir.rest.annotation.OptionalParam;
|
||||||
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||||
|
@ -292,7 +293,7 @@ public class MethodUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Integer findConditionalOperationParameterIndex(Method theMethod) {
|
public static Integer findConditionalOperationParameterIndex(Method theMethod) {
|
||||||
return MethodUtil.findParamAnnotationIndex(theMethod, ConditionalOperationParam.class);
|
return MethodUtil.findParamAnnotationIndex(theMethod, ConditionalUrlParam.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
|
@ -396,8 +397,10 @@ public class MethodUtil {
|
||||||
param = new SortParameter();
|
param = new SortParameter();
|
||||||
} else if (nextAnnotation instanceof TransactionParam) {
|
} else if (nextAnnotation instanceof TransactionParam) {
|
||||||
param = new TransactionParamBinder(theContext);
|
param = new TransactionParamBinder(theContext);
|
||||||
} else if (nextAnnotation instanceof ConditionalOperationParam) {
|
} else if (nextAnnotation instanceof ConditionalUrlParam) {
|
||||||
param = new ConditionalParamBinder(theRestfulOperationTypeEnum);
|
param = new ConditionalParamBinder(theRestfulOperationTypeEnum);
|
||||||
|
} else if (nextAnnotation instanceof OperationParam) {
|
||||||
|
param = new OperationParamBinder((OperationParam)nextAnnotation);
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hl7.fhir.instance.model.IBaseResource;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
@ -32,7 +34,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
class NullParameter implements IParameter {
|
class NullParameter implements IParameter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
|
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
|
||||||
//nothing
|
//nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,145 @@ package ca.uhn.fhir.rest.method;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.*;
|
import static org.apache.commons.lang3.StringUtils.*;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
|
||||||
|
import org.hl7.fhir.instance.model.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
import org.hl7.fhir.instance.model.api.IBaseParameters;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
|
import ca.uhn.fhir.model.api.Bundle;
|
||||||
|
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
|
||||||
|
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.parser.IParser;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Operation;
|
||||||
|
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
|
||||||
|
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||||
|
import ca.uhn.fhir.rest.server.IBundleProvider;
|
||||||
|
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
|
||||||
public class OperationMethodBinding {
|
public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
|
|
||||||
|
private Integer myIdParamIndex;
|
||||||
|
private String myName;
|
||||||
|
|
||||||
|
public OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider, Operation theAnnotation) {
|
||||||
|
super(theReturnResourceType, theMethod, theContext, theProvider);
|
||||||
|
|
||||||
|
myIdParamIndex = MethodUtil.findIdParameterIndex(theMethod);
|
||||||
|
myName = theAnnotation.name();
|
||||||
|
if (isBlank(myName)) {
|
||||||
|
throw new ConfigurationException("Method '" + theMethod.getName() + "' on type " + theMethod.getDeclaringClass().getName() + " is annotated with @" + Operation.class.getSimpleName() + " but this annotation has no name defined");
|
||||||
|
}
|
||||||
|
if (myName.startsWith("$") == false) {
|
||||||
|
myName = "$" + myName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theContext.getVersion().getVersion().isEquivalentTo(FhirVersionEnum.DSTU1)) {
|
||||||
|
throw new ConfigurationException("@" + Operation.class.getSimpleName() + " methods are not supported on servers for FHIR version " + theContext.getVersion().getVersion().name());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theReturnTypeFromRp != null) {
|
||||||
|
setResourceName(theContext.getResourceDefinition(theReturnTypeFromRp).getName());
|
||||||
|
} else {
|
||||||
|
if (Modifier.isAbstract(theAnnotation.type().getModifiers())== false) {
|
||||||
|
setResourceName(theContext.getResourceDefinition(theAnnotation.type()).getName());
|
||||||
|
} else {
|
||||||
|
setResourceName(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theMethod.getReturnType().isAssignableFrom(Bundle.class)) {
|
||||||
|
throw new ConfigurationException("Can not return a DSTU1 bundle from an @" + Operation.class.getSimpleName() + " method. Found in method " + theMethod.getName() + " defined in type " + theMethod.getDeclaringClass().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RestfulOperationTypeEnum getResourceOperationType() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BundleTypeEnum getResponseBundleType() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReturnTypeEnum getReturnType() {
|
||||||
|
return ReturnTypeEnum.RESOURCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RestfulOperationSystemEnum getSystemOperationType() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean incomingServerRequestMatchesMethod(Request theRequest) {
|
||||||
|
if (getResourceName() == null) {
|
||||||
|
if (isNotBlank(theRequest.getResourceName())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (!getResourceName().equals(theRequest.getResourceName())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean requestHasId = theRequest.getId() != null;
|
||||||
|
if (requestHasId != (myIdParamIndex != null)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return myName.equals(theRequest.getOperation());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object parseRequestObject(Request theRequest) throws IOException {
|
||||||
|
EncodingEnum encoding = RestfulServerUtils.determineRequestEncoding(theRequest);
|
||||||
|
IParser parser = encoding.newParser(getContext());
|
||||||
|
BufferedReader requestReader = theRequest.getServletRequest().getReader();
|
||||||
|
|
||||||
|
Class<? extends IBaseResource> wantedResourceType = getContext().getResourceDefinition("Parameters").getImplementingClass();
|
||||||
|
return parser.parseResource(wantedResourceType, requestReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseHttpClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
|
||||||
|
String id = null;
|
||||||
|
if (myIdParamIndex != null) {
|
||||||
|
IdDt idDt = (IdDt) theArgs[myIdParamIndex];
|
||||||
|
id = idDt.getValue();
|
||||||
|
}
|
||||||
|
IBaseParameters parameters = (IBaseParameters) getContext().getResourceDefinition("Parameters").newInstance();
|
||||||
|
|
||||||
|
if (theArgs != null) {
|
||||||
|
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||||
|
IParameter nextParam = getParameters().get(idx);
|
||||||
|
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null, parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return createOperationInvocation(getContext(), getResourceName(), id, myName, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object invokeServer(RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
|
||||||
|
if (myIdParamIndex != null) {
|
||||||
|
theMethodParams[myIdParamIndex] = theRequest.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
Object response = invokeServerMethod(theMethodParams);
|
||||||
|
IBundleProvider retVal = toResourceList(response);
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
public static HttpPostClientInvocation createOperationInvocation(FhirContext theContext, String theResourceName, String theId, String theOperationName, IBaseParameters theInput) {
|
public static HttpPostClientInvocation createOperationInvocation(FhirContext theContext, String theResourceName, String theId, String theOperationName, IBaseParameters theInput) {
|
||||||
StringBuilder b = new StringBuilder();
|
StringBuilder b = new StringBuilder();
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
package ca.uhn.fhir.rest.method;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - Core Library
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2015 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 java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hl7.fhir.instance.model.IBase;
|
||||||
|
import org.hl7.fhir.instance.model.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.IPrimitiveType;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseDatatype;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||||
|
import ca.uhn.fhir.context.BaseRuntimeChildDefinition.IAccessor;
|
||||||
|
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.RuntimeChildPrimitiveDatatypeDefinition;
|
||||||
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
|
import ca.uhn.fhir.rest.param.CollectionBinder;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
|
||||||
|
class OperationParamBinder implements IParameter {
|
||||||
|
|
||||||
|
private String myName;
|
||||||
|
private Class<?> myParameterType;
|
||||||
|
private Class<? extends Collection> myInnerCollectionType;
|
||||||
|
|
||||||
|
OperationParamBinder(OperationParam theAnnotation) {
|
||||||
|
myName = theAnnotation.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
|
||||||
|
assert theTargetResource != null;
|
||||||
|
if (theSourceClientArgument == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeResourceDefinition def = theContext.getResourceDefinition(theTargetResource);
|
||||||
|
|
||||||
|
BaseRuntimeChildDefinition paramChild = def.getChildByName("parameter");
|
||||||
|
BaseRuntimeElementCompositeDefinition<?> paramChildElem = (BaseRuntimeElementCompositeDefinition<?>) paramChild.getChildByName("parameter");
|
||||||
|
|
||||||
|
addClientParameter(theSourceClientArgument, theTargetResource, paramChild, paramChildElem);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addClientParameter(Object theSourceClientArgument, IBaseResource theTargetResource, BaseRuntimeChildDefinition paramChild, BaseRuntimeElementCompositeDefinition<?> paramChildElem) {
|
||||||
|
if (theSourceClientArgument instanceof IBaseResource) {
|
||||||
|
IBase parameter = createParameterRepetition(theTargetResource, paramChild, paramChildElem);
|
||||||
|
paramChildElem.getChildByName("resource").getMutator().addValue(parameter, (IBaseResource)theSourceClientArgument);
|
||||||
|
}else if (theSourceClientArgument instanceof IBaseDatatype) {
|
||||||
|
IBase parameter = createParameterRepetition(theTargetResource, paramChild, paramChildElem);
|
||||||
|
paramChildElem.getChildByName("value[x]").getMutator().addValue(parameter, (IBaseDatatype)theSourceClientArgument);
|
||||||
|
} else if (theSourceClientArgument instanceof Collection) {
|
||||||
|
Collection<?> collection = (Collection<?>) theSourceClientArgument;
|
||||||
|
for (Object next : collection) {
|
||||||
|
addClientParameter(next, theTargetResource, paramChild, paramChildElem);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Don't know how to handle value of type " + theSourceClientArgument.getClass() + " for paramater " + myName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IBase createParameterRepetition(IBaseResource theTargetResource, BaseRuntimeChildDefinition paramChild, BaseRuntimeElementCompositeDefinition<?> paramChildElem) {
|
||||||
|
IBase parameter = paramChildElem.newInstance();
|
||||||
|
paramChild.getMutator().addValue(theTargetResource, parameter);
|
||||||
|
paramChildElem.getChildByName("name").getMutator().addValue(parameter, new StringDt(myName));
|
||||||
|
return parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException {
|
||||||
|
FhirContext ctx = theRequest.getServer().getFhirContext();
|
||||||
|
IBaseResource requestContents = (IBaseResource) theRequestContents;
|
||||||
|
RuntimeResourceDefinition def = ctx.getResourceDefinition(requestContents);
|
||||||
|
|
||||||
|
BaseRuntimeChildDefinition paramChild = def.getChildByName("parameter");
|
||||||
|
BaseRuntimeElementCompositeDefinition<?> paramChildElem = (BaseRuntimeElementCompositeDefinition<?>) paramChild.getChildByName("parameter");
|
||||||
|
|
||||||
|
RuntimeChildPrimitiveDatatypeDefinition nameChild = (RuntimeChildPrimitiveDatatypeDefinition) paramChildElem.getChildByName("name");
|
||||||
|
BaseRuntimeChildDefinition valueChild = paramChildElem.getChildByName("value[x]");
|
||||||
|
BaseRuntimeChildDefinition resourceChild = paramChildElem.getChildByName("resource");
|
||||||
|
|
||||||
|
IAccessor paramChildAccessor = paramChild.getAccessor();
|
||||||
|
List<IBase> values = paramChildAccessor.getValues(requestContents);
|
||||||
|
List<Object> matchingParamValues = new ArrayList<Object>();
|
||||||
|
for (IBase nextParameter : values) {
|
||||||
|
List<IBase> nextNames = nameChild.getAccessor().getValues(nextParameter);
|
||||||
|
if (nextNames != null && nextNames.size() > 0) {
|
||||||
|
IPrimitiveType<?> nextName = (IPrimitiveType<?>) nextNames.get(0);
|
||||||
|
if (myName.equals(nextName.getValueAsString())) {
|
||||||
|
|
||||||
|
if (myParameterType.isAssignableFrom(nextParameter.getClass())) {
|
||||||
|
matchingParamValues.add(nextParameter);
|
||||||
|
} else {
|
||||||
|
List<IBase> paramValues = valueChild.getAccessor().getValues(nextParameter);
|
||||||
|
List<IBase> paramResources = resourceChild.getAccessor().getValues(nextParameter);
|
||||||
|
if (paramValues != null && paramValues.size() > 0) {
|
||||||
|
tryToAddValues(paramValues, matchingParamValues);
|
||||||
|
} else if (paramResources != null && paramResources.size() > 0) {
|
||||||
|
tryToAddValues(paramResources, matchingParamValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchingParamValues.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (myInnerCollectionType == null) {
|
||||||
|
return matchingParamValues.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Collection retVal = myInnerCollectionType.newInstance();
|
||||||
|
retVal.addAll(matchingParamValues);
|
||||||
|
return retVal;
|
||||||
|
} catch (InstantiationException e) {
|
||||||
|
throw new InternalErrorException("Failed to instantiate " + myInnerCollectionType, e);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new InternalErrorException("Failed to instantiate " + myInnerCollectionType, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryToAddValues(List<IBase> theParamValues, List<Object> theMatchingParamValues) {
|
||||||
|
for (IBase nextValue : theParamValues) {
|
||||||
|
if (nextValue == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!myParameterType.isAssignableFrom(nextValue.getClass())) {
|
||||||
|
throw new InvalidRequestException("Request has parameter " + myName + " of type " + nextValue.getClass().getSimpleName() + " but method expects type " + myParameterType.getSimpleName());
|
||||||
|
}
|
||||||
|
theMatchingParamValues.add(nextValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
|
||||||
|
myParameterType = theParameterType;
|
||||||
|
if (theInnerCollectionType != null) {
|
||||||
|
myInnerCollectionType = CollectionBinder.getInstantiableCollectionType(theInnerCollectionType, myName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -164,7 +164,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
|
||||||
|
|
||||||
for (int idx = 0; idx < theArgs.length; idx++) {
|
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||||
IParameter nextParam = getParameters().get(idx);
|
IParameter nextParam = getParameters().get(idx);
|
||||||
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null);
|
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return retVal;
|
return retVal;
|
||||||
|
|
|
@ -267,7 +267,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
if (theArgs != null) {
|
if (theArgs != null) {
|
||||||
for (int idx = 0; idx < theArgs.length; idx++) {
|
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||||
IParameter nextParam = getParameters().get(idx);
|
IParameter nextParam = getParameters().get(idx);
|
||||||
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], queryStringArgs);
|
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], queryStringArgs, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,8 @@ import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hl7.fhir.instance.model.IBaseResource;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
@ -33,7 +35,7 @@ class ServerBaseParamBinder implements IParameter {
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerBaseParamBinder.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerBaseParamBinder.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
|
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
|
||||||
/*
|
/*
|
||||||
* Does nothing, since we just ignore serverbase arguments
|
* Does nothing, since we just ignore serverbase arguments
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -25,6 +25,8 @@ import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hl7.fhir.instance.model.IBaseResource;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
@ -33,7 +35,7 @@ class ServletRequestParameter implements IParameter {
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletRequestParameter.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletRequestParameter.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
|
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
|
||||||
/*
|
/*
|
||||||
* Does nothing, since we just ignore HttpServletRequest arguments
|
* Does nothing, since we just ignore HttpServletRequest arguments
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -25,6 +25,8 @@ import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hl7.fhir.instance.model.IBaseResource;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
@ -33,7 +35,7 @@ class ServletResponseParameter implements IParameter {
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletResponseParameter.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletResponseParameter.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
|
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
|
||||||
/*
|
/*
|
||||||
* Does nothing, since we just ignore HttpServletResponse arguments
|
* Does nothing, since we just ignore HttpServletResponse arguments
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -27,6 +27,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.hl7.fhir.instance.model.IBaseResource;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
@ -43,7 +44,7 @@ class SinceParameter implements IParameter {
|
||||||
private Class<?> myType;
|
private Class<?> myType;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
|
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
|
||||||
if (theSourceClientArgument != null) {
|
if (theSourceClientArgument != null) {
|
||||||
InstantDt since = ParameterUtil.toInstant(theSourceClientArgument);
|
InstantDt since = ParameterUtil.toInstant(theSourceClientArgument);
|
||||||
if (since.isEmpty() == false) {
|
if (since.isEmpty() == false) {
|
||||||
|
|
|
@ -28,6 +28,8 @@ import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hl7.fhir.instance.model.IBaseResource;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.rest.annotation.Sort;
|
import ca.uhn.fhir.rest.annotation.Sort;
|
||||||
|
@ -40,7 +42,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
public class SortParameter implements IParameter {
|
public class SortParameter implements IParameter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
|
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
|
||||||
SortSpec ss = (SortSpec) theSourceClientArgument;
|
SortSpec ss = (SortSpec) theSourceClientArgument;
|
||||||
while (ss != null) {
|
while (ss != null) {
|
||||||
String name;
|
String name;
|
||||||
|
|
|
@ -90,7 +90,7 @@ class TransactionParamBinder implements IParameter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
|
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
|
||||||
// nothing
|
// nothing
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,7 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP
|
||||||
|
|
||||||
for (int idx = 0; idx < theArgs.length; idx++) {
|
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||||
IParameter nextParam = getParameters().get(idx);
|
IParameter nextParam = getParameters().get(idx);
|
||||||
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null);
|
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return retVal;
|
return retVal;
|
||||||
|
|
|
@ -74,7 +74,7 @@ public class ValidateMethodBinding extends BaseOutcomeReturningMethodBindingWith
|
||||||
|
|
||||||
for (int idx = 0; idx < theArgs.length; idx++) {
|
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||||
IParameter nextParam = getParameters().get(idx);
|
IParameter nextParam = getParameters().get(idx);
|
||||||
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null);
|
nextParam.translateClientArgumentIntoQueryArgument(getContext(), theArgs[idx], null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return retVal;
|
return retVal;
|
||||||
|
|
|
@ -28,6 +28,7 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.hl7.fhir.instance.model.IBaseResource;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum;
|
import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum;
|
||||||
|
@ -98,7 +99,7 @@ public abstract class BaseQueryParameter implements IParameter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
|
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
|
||||||
if (theSourceClientArgument == null) {
|
if (theSourceClientArgument == null) {
|
||||||
if (isRequired()) {
|
if (isRequired()) {
|
||||||
throw new NullPointerException("SearchParameter '" + getName() + "' is required and may not be null");
|
throw new NullPointerException("SearchParameter '" + getName() + "' is required and may not be null");
|
||||||
|
|
|
@ -25,6 +25,8 @@ import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hl7.fhir.instance.model.IBaseResource;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.rest.method.IParameter;
|
import ca.uhn.fhir.rest.method.IParameter;
|
||||||
|
@ -41,7 +43,7 @@ public class ResourceParameter implements IParameter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
|
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,6 @@ public class RestfulServer extends HttpServlet {
|
||||||
private FhirContext myFhirContext;
|
private FhirContext myFhirContext;
|
||||||
private String myImplementationDescription;
|
private String myImplementationDescription;
|
||||||
private final List<IServerInterceptor> myInterceptors = new ArrayList<IServerInterceptor>();
|
private final List<IServerInterceptor> myInterceptors = new ArrayList<IServerInterceptor>();
|
||||||
private ResourceBinding myNullResourceBinding = new ResourceBinding();
|
|
||||||
private IPagingProvider myPagingProvider;
|
private IPagingProvider myPagingProvider;
|
||||||
private Collection<Object> myPlainProviders;
|
private Collection<Object> myPlainProviders;
|
||||||
private Map<String, ResourceBinding> myResourceNameToProvider = new HashMap<String, ResourceBinding>();
|
private Map<String, ResourceBinding> myResourceNameToProvider = new HashMap<String, ResourceBinding>();
|
||||||
|
@ -119,7 +118,8 @@ public class RestfulServer extends HttpServlet {
|
||||||
/**
|
/**
|
||||||
* This method is called prior to sending a response to incoming requests. It is used to add custom headers.
|
* This method is called prior to sending a response to incoming requests. It is used to add custom headers.
|
||||||
* <p>
|
* <p>
|
||||||
* Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid inadvertantly disabling functionality.
|
* Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid
|
||||||
|
* inadvertantly disabling functionality.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
|
public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
|
||||||
|
@ -221,7 +221,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
String resourceName = foundMethodBinding.getResourceName();
|
String resourceName = foundMethodBinding.getResourceName();
|
||||||
ResourceBinding resourceBinding;
|
ResourceBinding resourceBinding;
|
||||||
if (resourceName == null) {
|
if (resourceName == null) {
|
||||||
resourceBinding = myNullResourceBinding;
|
resourceBinding = myServerBinding;
|
||||||
} else {
|
} else {
|
||||||
RuntimeResourceDefinition definition = myFhirContext.getResourceDefinition(resourceName);
|
RuntimeResourceDefinition definition = myFhirContext.getResourceDefinition(resourceName);
|
||||||
if (myResourceNameToProvider.containsKey(definition.getName())) {
|
if (myResourceNameToProvider.containsKey(definition.getName())) {
|
||||||
|
@ -263,6 +263,8 @@ public class RestfulServer extends HttpServlet {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ResourceBinding myServerBinding = new ResourceBinding();
|
||||||
|
|
||||||
private void findSystemMethods(Object theSystemProvider, Class<?> clazz) {
|
private void findSystemMethods(Object theSystemProvider, Class<?> clazz) {
|
||||||
Class<?> supertype = clazz.getSuperclass();
|
Class<?> supertype = clazz.getSuperclass();
|
||||||
if (!Object.class.equals(supertype)) {
|
if (!Object.class.equals(supertype)) {
|
||||||
|
@ -277,6 +279,8 @@ public class RestfulServer extends HttpServlet {
|
||||||
if (foundMethodBinding != null) {
|
if (foundMethodBinding != null) {
|
||||||
if (foundMethodBinding instanceof ConformanceMethodBinding) {
|
if (foundMethodBinding instanceof ConformanceMethodBinding) {
|
||||||
myServerConformanceMethod = foundMethodBinding;
|
myServerConformanceMethod = foundMethodBinding;
|
||||||
|
} else {
|
||||||
|
myServerBinding.addMethod(foundMethodBinding);
|
||||||
}
|
}
|
||||||
ourLog.info(" * Method: {}#{} is a handler", theSystemProvider.getClass(), m.getName());
|
ourLog.info(" * Method: {}#{} is a handler", theSystemProvider.getClass(), m.getName());
|
||||||
} else {
|
} else {
|
||||||
|
@ -296,8 +300,9 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with the <code>_format</code> URL parameter, or with an <code>Accept</code> header
|
* Returns the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either
|
||||||
* in the request. The default is {@link EncodingEnum#XML}.
|
* with the <code>_format</code> URL parameter, or with an <code>Accept</code> header in the request. The default is
|
||||||
|
* {@link EncodingEnum#XML}.
|
||||||
*/
|
*/
|
||||||
public EncodingEnum getDefaultResponseEncoding() {
|
public EncodingEnum getDefaultResponseEncoding() {
|
||||||
return myDefaultResponseEncoding;
|
return myDefaultResponseEncoding;
|
||||||
|
@ -311,8 +316,8 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain providers should generally use this context if one is needed, as opposed to
|
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain
|
||||||
* creating their own.
|
* providers should generally use this context if one is needed, as opposed to creating their own.
|
||||||
*/
|
*/
|
||||||
public FhirContext getFhirContext() {
|
public FhirContext getFhirContext() {
|
||||||
return myFhirContext;
|
return myFhirContext;
|
||||||
|
@ -343,7 +348,8 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows users of RestfulServer to override the getRequestPath method to let them build their custom request path implementation
|
* Allows users of RestfulServer to override the getRequestPath method to let them build their custom request path
|
||||||
|
* implementation
|
||||||
*
|
*
|
||||||
* @param requestFullPath
|
* @param requestFullPath
|
||||||
* the full request path
|
* the full request path
|
||||||
|
@ -369,7 +375,8 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the server address strategy, which is used to determine what base URL to provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
|
* Get the server address strategy, which is used to determine what base URL to provide clients to refer to this
|
||||||
|
* server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
|
||||||
*/
|
*/
|
||||||
public IServerAddressStrategy getServerAddressStrategy() {
|
public IServerAddressStrategy getServerAddressStrategy() {
|
||||||
return myServerAddressStrategy;
|
return myServerAddressStrategy;
|
||||||
|
@ -386,9 +393,11 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement if one has been explicitly defined.
|
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
|
||||||
|
* (metadata) statement if one has been explicitly defined.
|
||||||
* <p>
|
* <p>
|
||||||
* By default, the ServerConformanceProvider for the declared version of FHIR is used, but this can be changed, or set to <code>null</code> to use the appropriate one for the given FHIR version.
|
* By default, the ServerConformanceProvider for the declared version of FHIR is used, but this can be changed, or
|
||||||
|
* set to <code>null</code> to use the appropriate one for the given FHIR version.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public Object getServerConformanceProvider() {
|
public Object getServerConformanceProvider() {
|
||||||
|
@ -396,7 +405,8 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the server's name, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
|
* Gets the server's name, as exported in conformance profiles exported by the server. This is informational only,
|
||||||
|
* but can be helpful to set with something appropriate.
|
||||||
*
|
*
|
||||||
* @see RestfulServer#setServerName(String)
|
* @see RestfulServer#setServerName(String)
|
||||||
*/
|
*/
|
||||||
|
@ -409,7 +419,8 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
|
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational
|
||||||
|
* only, but can be helpful to set with something appropriate.
|
||||||
*/
|
*/
|
||||||
public String getServerVersion() {
|
public String getServerVersion() {
|
||||||
return myServerVersion;
|
return myServerVersion;
|
||||||
|
@ -449,8 +460,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
boolean respondGzip = theRequest.isRespondGzip();
|
boolean respondGzip = theRequest.isRespondGzip();
|
||||||
|
|
||||||
IVersionSpecificBundleFactory bundleFactory = myFhirContext.newBundleFactory();
|
IVersionSpecificBundleFactory bundleFactory = myFhirContext.newBundleFactory();
|
||||||
bundleFactory.initializeBundleFromBundleProvider(this, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, start, count, thePagingAction,
|
bundleFactory.initializeBundleFromBundleProvider(this, resultList, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, start, count, thePagingAction, null);
|
||||||
null);
|
|
||||||
|
|
||||||
Bundle bundle = bundleFactory.getDstu1Bundle();
|
Bundle bundle = bundleFactory.getDstu1Bundle();
|
||||||
if (bundle != null) {
|
if (bundle != null) {
|
||||||
|
@ -473,8 +483,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RestfulServerUtils.streamResponseAsResource(this, theResponse, (IResource) resBundle, responseEncoding, prettyPrint, requestIsBrowser, narrativeMode, Constants.STATUS_HTTP_200_OK,
|
RestfulServerUtils.streamResponseAsResource(this, theResponse, (IResource) resBundle, responseEncoding, prettyPrint, requestIsBrowser, narrativeMode, Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), theRequest.getFhirServerBase());
|
||||||
theRequest.isRespondGzip(), theRequest.getFhirServerBase());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -530,7 +539,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
StringTokenizer tok = new StringTokenizer(requestPath, "/");
|
StringTokenizer tok = new StringTokenizer(requestPath, "/");
|
||||||
if (tok.hasMoreTokens()) {
|
if (tok.hasMoreTokens()) {
|
||||||
resourceName = tok.nextToken();
|
resourceName = tok.nextToken();
|
||||||
if (resourceName.startsWith("_")) {
|
if (partIsOperation(resourceName)) {
|
||||||
operation = resourceName;
|
operation = resourceName;
|
||||||
resourceName = null;
|
resourceName = null;
|
||||||
}
|
}
|
||||||
|
@ -541,7 +550,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
if (Constants.URL_TOKEN_METADATA.equals(resourceName) || theRequestType == RequestType.OPTIONS) {
|
if (Constants.URL_TOKEN_METADATA.equals(resourceName) || theRequestType == RequestType.OPTIONS) {
|
||||||
resourceMethod = myServerConformanceMethod;
|
resourceMethod = myServerConformanceMethod;
|
||||||
} else if (resourceName == null) {
|
} else if (resourceName == null) {
|
||||||
resourceBinding = myNullResourceBinding;
|
resourceBinding = myServerBinding;
|
||||||
} else {
|
} else {
|
||||||
resourceBinding = myResourceNameToProvider.get(resourceName);
|
resourceBinding = myResourceNameToProvider.get(resourceName);
|
||||||
if (resourceBinding == null) {
|
if (resourceBinding == null) {
|
||||||
|
@ -551,7 +560,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
|
|
||||||
if (tok.hasMoreTokens()) {
|
if (tok.hasMoreTokens()) {
|
||||||
String nextString = tok.nextToken();
|
String nextString = tok.nextToken();
|
||||||
if (nextString.startsWith("_")) {
|
if (partIsOperation(nextString)) {
|
||||||
operation = nextString;
|
operation = nextString;
|
||||||
} else {
|
} else {
|
||||||
id = new IdDt(resourceName, UrlUtil.unescape(nextString));
|
id = new IdDt(resourceName, UrlUtil.unescape(nextString));
|
||||||
|
@ -570,10 +579,10 @@ public class RestfulServer extends HttpServlet {
|
||||||
} else {
|
} else {
|
||||||
operation = Constants.PARAM_HISTORY;
|
operation = Constants.PARAM_HISTORY;
|
||||||
}
|
}
|
||||||
} else if (nextString.startsWith("_")) {
|
} else if (partIsOperation(nextString)) {
|
||||||
// FIXME: this would be untrue for _meta/_delete
|
// FIXME: this would be untrue for _meta/_delete
|
||||||
if (operation != null) {
|
if (operation != null) {
|
||||||
throw new InvalidRequestException("URL Path contains two operations (part beginning with _): " + requestPath);
|
throw new InvalidRequestException("URL Path contains two operations: " + requestPath);
|
||||||
}
|
}
|
||||||
operation = nextString;
|
operation = nextString;
|
||||||
} else {
|
} else {
|
||||||
|
@ -637,8 +646,10 @@ public class RestfulServer extends HttpServlet {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resourceMethod == null && resourceBinding != null) {
|
if (resourceMethod == null) {
|
||||||
resourceMethod = resourceBinding.getMethod(r);
|
if (resourceBinding != null) {
|
||||||
|
resourceMethod = resourceBinding.getMethod(r);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (resourceMethod == null) {
|
if (resourceMethod == null) {
|
||||||
StringBuilder b = new StringBuilder();
|
StringBuilder b = new StringBuilder();
|
||||||
|
@ -766,8 +777,9 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations, but subclasses may put initialization code in {@link #initialize()}, which is
|
* Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations,
|
||||||
* called immediately before beginning initialization of the restful server's internal init.
|
* but subclasses may put initialization code in {@link #initialize()}, which is called immediately before beginning
|
||||||
|
* initialization of the restful server's internal init.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final void init() throws ServletException {
|
public final void init() throws ServletException {
|
||||||
|
@ -790,8 +802,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
|
|
||||||
String resourceName = myFhirContext.getResourceDefinition(resourceType).getName();
|
String resourceName = myFhirContext.getResourceDefinition(resourceType).getName();
|
||||||
if (typeToProvider.containsKey(resourceName)) {
|
if (typeToProvider.containsKey(resourceName)) {
|
||||||
throw new ServletException("Multiple resource providers return resource type[" + resourceName + "]: First[" + typeToProvider.get(resourceName).getClass().getCanonicalName()
|
throw new ServletException("Multiple resource providers return resource type[" + resourceName + "]: First[" + typeToProvider.get(resourceName).getClass().getCanonicalName() + "] and Second[" + nextProvider.getClass().getCanonicalName() + "]");
|
||||||
+ "] and Second[" + nextProvider.getClass().getCanonicalName() + "]");
|
|
||||||
}
|
}
|
||||||
typeToProvider.put(resourceName, nextProvider);
|
typeToProvider.put(resourceName, nextProvider);
|
||||||
providedResourceScanner.scanForProvidedResources(nextProvider);
|
providedResourceScanner.scanForProvidedResources(nextProvider);
|
||||||
|
@ -829,11 +840,13 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the server being used.
|
* This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the
|
||||||
|
* server being used.
|
||||||
*
|
*
|
||||||
* @throws ServletException
|
* @throws ServletException
|
||||||
* If the initialization failed. Note that you should consider throwing {@link UnavailableException} (which extends {@link ServletException}), as this is a flag to the servlet
|
* If the initialization failed. Note that you should consider throwing {@link UnavailableException}
|
||||||
* container that the servlet is not usable.
|
* (which extends {@link ServletException}), as this is a flag to the servlet container that the servlet
|
||||||
|
* is not usable.
|
||||||
*/
|
*/
|
||||||
protected void initialize() throws ServletException {
|
protected void initialize() throws ServletException {
|
||||||
// nothing by default
|
// nothing by default
|
||||||
|
@ -880,8 +893,9 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER} (which is the default), the server will automatically add a profile tag based
|
* Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER}
|
||||||
* on the class of the resource(s) being returned.
|
* (which is the default), the server will automatically add a profile tag based on the class of the resource(s)
|
||||||
|
* being returned.
|
||||||
*
|
*
|
||||||
* @param theAddProfileTag
|
* @param theAddProfileTag
|
||||||
* The behaviour enum (must not be null)
|
* The behaviour enum (must not be null)
|
||||||
|
@ -892,8 +906,9 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with the <code>_format</code> URL parameter, or with an <code>Accept</code> header in
|
* Sets the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with
|
||||||
* the request. The default is {@link EncodingEnum#XML}.
|
* the <code>_format</code> URL parameter, or with an <code>Accept</code> header in the request. The default is
|
||||||
|
* {@link EncodingEnum#XML}.
|
||||||
*/
|
*/
|
||||||
public void setDefaultResponseEncoding(EncodingEnum theDefaultResponseEncoding) {
|
public void setDefaultResponseEncoding(EncodingEnum theDefaultResponseEncoding) {
|
||||||
Validate.notNull(theDefaultResponseEncoding, "theDefaultResponseEncoding can not be null");
|
Validate.notNull(theDefaultResponseEncoding, "theDefaultResponseEncoding can not be null");
|
||||||
|
@ -901,7 +916,8 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets (enables/disables) the server support for ETags. Must not be <code>null</code>. Default is {@link #DEFAULT_ETAG_SUPPORT}
|
* Sets (enables/disables) the server support for ETags. Must not be <code>null</code>. Default is
|
||||||
|
* {@link #DEFAULT_ETAG_SUPPORT}
|
||||||
*
|
*
|
||||||
* @param theETagSupport
|
* @param theETagSupport
|
||||||
* The ETag support mode
|
* The ETag support mode
|
||||||
|
@ -997,7 +1013,8 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
|
* Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this
|
||||||
|
* server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
|
||||||
*/
|
*/
|
||||||
public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) {
|
public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) {
|
||||||
Validate.notNull(theServerAddressStrategy, "Server address strategy can not be null");
|
Validate.notNull(theServerAddressStrategy, "Server address strategy can not be null");
|
||||||
|
@ -1005,15 +1022,17 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement.
|
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
|
||||||
|
* (metadata) statement.
|
||||||
* <p>
|
* <p>
|
||||||
* By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can be changed, or set to <code>null</code> if you do not wish to export a
|
* By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can
|
||||||
* conformance statement.
|
* be changed, or set to <code>null</code> if you do not wish to export a conformance statement.
|
||||||
* </p>
|
* </p>
|
||||||
* Note that this method can only be called before the server is initialized.
|
* Note that this method can only be called before the server is initialized.
|
||||||
*
|
*
|
||||||
* @throws IllegalStateException
|
* @throws IllegalStateException
|
||||||
* Note that this method can only be called prior to {@link #init() initialization} and will throw an {@link IllegalStateException} if called after that.
|
* Note that this method can only be called prior to {@link #init() initialization} and will throw an
|
||||||
|
* {@link IllegalStateException} if called after that.
|
||||||
*/
|
*/
|
||||||
public void setServerConformanceProvider(Object theServerConformanceProvider) {
|
public void setServerConformanceProvider(Object theServerConformanceProvider) {
|
||||||
if (myStarted) {
|
if (myStarted) {
|
||||||
|
@ -1023,22 +1042,24 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the server's name, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
|
* Sets the server's name, as exported in conformance profiles exported by the server. This is informational only,
|
||||||
|
* but can be helpful to set with something appropriate.
|
||||||
*/
|
*/
|
||||||
public void setServerName(String theServerName) {
|
public void setServerName(String theServerName) {
|
||||||
myServerName = theServerName;
|
myServerName = theServerName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
|
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational
|
||||||
|
* only, but can be helpful to set with something appropriate.
|
||||||
*/
|
*/
|
||||||
public void setServerVersion(String theServerVersion) {
|
public void setServerVersion(String theServerVersion) {
|
||||||
myServerVersion = theServerVersion;
|
myServerVersion = theServerVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set to <code>true</code> (default is false), the server will use browser friendly content-types (instead of standard FHIR ones) when it detects that the request is coming from a browser
|
* If set to <code>true</code> (default is false), the server will use browser friendly content-types (instead of
|
||||||
* instead of a FHIR
|
* standard FHIR ones) when it detects that the request is coming from a browser instead of a FHIR
|
||||||
*/
|
*/
|
||||||
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
|
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
|
||||||
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
|
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
|
||||||
|
@ -1057,6 +1078,10 @@ public class RestfulServer extends HttpServlet {
|
||||||
theResponse.getWriter().write(theException.getMessage());
|
theResponse.getWriter().write(theException.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean partIsOperation(String nextString) {
|
||||||
|
return nextString.length() > 0 && (nextString.charAt(0) == '_' || nextString.charAt(0) == '$');
|
||||||
|
}
|
||||||
|
|
||||||
public enum NarrativeModeEnum {
|
public enum NarrativeModeEnum {
|
||||||
NORMAL, ONLY, SUPPRESS;
|
NORMAL, ONLY, SUPPRESS;
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.rest.annotation.ConditionalOperationParam;
|
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Create;
|
import ca.uhn.fhir.rest.annotation.Create;
|
||||||
import ca.uhn.fhir.rest.annotation.Delete;
|
import ca.uhn.fhir.rest.annotation.Delete;
|
||||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
|
@ -47,7 +47,7 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
|
||||||
}
|
}
|
||||||
|
|
||||||
@Create
|
@Create
|
||||||
public MethodOutcome create(HttpServletRequest theRequest, @ResourceParam T theResource, @ConditionalOperationParam String theConditional) {
|
public MethodOutcome create(HttpServletRequest theRequest, @ResourceParam T theResource, @ConditionalUrlParam String theConditional) {
|
||||||
startRequest(theRequest);
|
startRequest(theRequest);
|
||||||
try {
|
try {
|
||||||
if (theConditional != null) {
|
if (theConditional != null) {
|
||||||
|
@ -61,7 +61,7 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
public MethodOutcome delete(HttpServletRequest theRequest, @IdParam IdDt theResource, @ConditionalOperationParam String theConditional) {
|
public MethodOutcome delete(HttpServletRequest theRequest, @IdParam IdDt theResource, @ConditionalUrlParam String theConditional) {
|
||||||
startRequest(theRequest);
|
startRequest(theRequest);
|
||||||
try {
|
try {
|
||||||
if (theConditional != null) {
|
if (theConditional != null) {
|
||||||
|
@ -75,7 +75,7 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
|
||||||
}
|
}
|
||||||
|
|
||||||
@Update
|
@Update
|
||||||
public MethodOutcome update(HttpServletRequest theRequest, @ResourceParam T theResource, @IdParam IdDt theId, @ConditionalOperationParam String theConditional) {
|
public MethodOutcome update(HttpServletRequest theRequest, @ResourceParam T theResource, @IdParam IdDt theId, @ConditionalUrlParam String theConditional) {
|
||||||
startRequest(theRequest);
|
startRequest(theRequest);
|
||||||
try {
|
try {
|
||||||
if (theConditional != null) {
|
if (theConditional != null) {
|
||||||
|
|
|
@ -71,6 +71,7 @@ public class BundleTypeTest {
|
||||||
assertTrue("Expected request of type POST on long params list", value instanceof HttpPost);
|
assertTrue("Expected request of type POST on long params list", value instanceof HttpPost);
|
||||||
HttpPost post = (HttpPost) value;
|
HttpPost post = (HttpPost) value;
|
||||||
String body = IOUtils.toString(post.getEntity().getContent());
|
String body = IOUtils.toString(post.getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(post.getEntity().getContent());
|
||||||
ourLog.info(body);
|
ourLog.info(body);
|
||||||
|
|
||||||
assertThat(body, Matchers.containsString("<type value=\"" + BundleTypeEnum.TRANSACTION.getCode()));
|
assertThat(body, Matchers.containsString("<type value=\"" + BundleTypeEnum.TRANSACTION.getCode()));
|
||||||
|
|
|
@ -0,0 +1,306 @@
|
||||||
|
package ca.uhn.fhir.rest.client;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.commons.io.input.ReaderInputStream;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.ProtocolVersion;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.apache.http.message.BasicHeader;
|
||||||
|
import org.apache.http.message.BasicStatusLine;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
|
||||||
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.Parameters;
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||||
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
|
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.client.api.IBasicClient;
|
||||||
|
import ca.uhn.fhir.rest.server.Constants;
|
||||||
|
|
||||||
|
public class OperationClientTest {
|
||||||
|
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationClientTest.class);
|
||||||
|
private FhirContext ourCtx;
|
||||||
|
private HttpClient ourHttpClient;
|
||||||
|
|
||||||
|
private HttpResponse ourHttpResponse;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
ourCtx = FhirContext.forDstu2();
|
||||||
|
|
||||||
|
ourHttpClient = mock(HttpClient.class, new ReturnsDeepStubs());
|
||||||
|
ourCtx.getRestfulClientFactory().setHttpClient(ourHttpClient);
|
||||||
|
ourCtx.getRestfulClientFactory().setServerValidationModeEnum(ServerValidationModeEnum.NEVER);
|
||||||
|
|
||||||
|
ourHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpInstance() throws Exception {
|
||||||
|
Parameters outParams = new Parameters();
|
||||||
|
outParams.addParameter().setName("FOO");
|
||||||
|
final String retVal = ourCtx.newXmlParser().encodeResourceToString(outParams);
|
||||||
|
|
||||||
|
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||||
|
when(ourHttpClient.execute(capt.capture())).thenReturn(ourHttpResponse);
|
||||||
|
when(ourHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||||
|
when(ourHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||||
|
when(ourHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
|
||||||
|
@Override
|
||||||
|
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||||
|
return new ReaderInputStream(new StringReader(retVal), Charset.forName("UTF-8"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
IOpClient client = ourCtx.newRestfulClient(IOpClient.class, "http://foo");
|
||||||
|
|
||||||
|
int idx = 0;
|
||||||
|
|
||||||
|
Parameters response = client.opInstance(new IdDt("222"), new StringDt("PARAM1str"), new Patient().setActive(true));
|
||||||
|
assertEquals("FOO", response.getParameter().get(0).getName());
|
||||||
|
HttpPost value = (HttpPost) capt.getAllValues().get(idx);
|
||||||
|
String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent());
|
||||||
|
ourLog.info(requestBody);
|
||||||
|
Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody);
|
||||||
|
assertEquals("http://foo/Patient/222/$OP_INSTANCE", value.getURI().toASCIIString());
|
||||||
|
assertEquals(2, request.getParameter().size());
|
||||||
|
assertEquals("PARAM1", request.getParameter().get(0).getName());
|
||||||
|
assertEquals("PARAM1str", ((StringDt) request.getParameter().get(0).getValue()).getValue());
|
||||||
|
assertEquals("PARAM2", request.getParameter().get(1).getName());
|
||||||
|
assertEquals(Boolean.TRUE, ((Patient) request.getParameter().get(1).getResource()).getActive());
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpServer() throws Exception {
|
||||||
|
Parameters outParams = new Parameters();
|
||||||
|
outParams.addParameter().setName("FOO");
|
||||||
|
final String retVal = ourCtx.newXmlParser().encodeResourceToString(outParams);
|
||||||
|
|
||||||
|
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||||
|
when(ourHttpClient.execute(capt.capture())).thenReturn(ourHttpResponse);
|
||||||
|
when(ourHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||||
|
when(ourHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||||
|
when(ourHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
|
||||||
|
@Override
|
||||||
|
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||||
|
return new ReaderInputStream(new StringReader(retVal), Charset.forName("UTF-8"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
IOpClient client = ourCtx.newRestfulClient(IOpClient.class, "http://foo");
|
||||||
|
|
||||||
|
int idx = 0;
|
||||||
|
|
||||||
|
Parameters response = client.opServer(new StringDt("PARAM1str"), new Patient().setActive(true));
|
||||||
|
assertEquals("FOO", response.getParameter().get(0).getName());
|
||||||
|
HttpPost value = (HttpPost) capt.getAllValues().get(idx);
|
||||||
|
String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent());
|
||||||
|
ourLog.info(requestBody);
|
||||||
|
Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody);
|
||||||
|
assertEquals("http://foo/$OP_SERVER", value.getURI().toASCIIString());
|
||||||
|
assertEquals(2, request.getParameter().size());
|
||||||
|
assertEquals("PARAM1", request.getParameter().get(0).getName());
|
||||||
|
assertEquals("PARAM1str", ((StringDt) request.getParameter().get(0).getValue()).getValue());
|
||||||
|
assertEquals("PARAM2", request.getParameter().get(1).getName());
|
||||||
|
assertEquals(Boolean.TRUE, ((Patient) request.getParameter().get(1).getResource()).getActive());
|
||||||
|
idx++;
|
||||||
|
|
||||||
|
response = client.opServer(null, new Patient().setActive(true));
|
||||||
|
assertEquals("FOO", response.getParameter().get(0).getName());
|
||||||
|
value = (HttpPost) capt.getAllValues().get(idx);
|
||||||
|
requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent());
|
||||||
|
ourLog.info(requestBody);
|
||||||
|
request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody);
|
||||||
|
assertEquals(1, request.getParameter().size());
|
||||||
|
assertEquals("PARAM2", request.getParameter().get(0).getName());
|
||||||
|
assertEquals(Boolean.TRUE, ((Patient) request.getParameter().get(0).getResource()).getActive());
|
||||||
|
idx++;
|
||||||
|
|
||||||
|
response = client.opServer(null, null);
|
||||||
|
assertEquals("FOO", response.getParameter().get(0).getName());
|
||||||
|
value = (HttpPost) capt.getAllValues().get(idx);
|
||||||
|
requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent());
|
||||||
|
ourLog.info(requestBody);
|
||||||
|
request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody);
|
||||||
|
assertEquals(0, request.getParameter().size());
|
||||||
|
idx++;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpWithListParam() throws Exception {
|
||||||
|
Parameters outParams = new Parameters();
|
||||||
|
outParams.addParameter().setName("FOO");
|
||||||
|
final String retVal = ourCtx.newXmlParser().encodeResourceToString(outParams);
|
||||||
|
|
||||||
|
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||||
|
when(ourHttpClient.execute(capt.capture())).thenReturn(ourHttpResponse);
|
||||||
|
when(ourHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||||
|
when(ourHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||||
|
when(ourHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
|
||||||
|
@Override
|
||||||
|
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||||
|
return new ReaderInputStream(new StringReader(retVal), Charset.forName("UTF-8"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
IOpClient client = ourCtx.newRestfulClient(IOpClient.class, "http://foo");
|
||||||
|
|
||||||
|
int idx = 0;
|
||||||
|
|
||||||
|
Parameters response = client.opServerListParam(new Patient().setActive(true), Arrays.asList(new StringDt("PARAM3str1"), new StringDt("PARAM3str2")));
|
||||||
|
assertEquals("FOO", response.getParameter().get(0).getName());
|
||||||
|
HttpPost value = (HttpPost) capt.getAllValues().get(idx);
|
||||||
|
String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent());
|
||||||
|
ourLog.info(requestBody);
|
||||||
|
Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody);
|
||||||
|
assertEquals("http://foo/$OP_SERVER_LIST_PARAM", value.getURI().toASCIIString());
|
||||||
|
assertEquals(3, request.getParameter().size());
|
||||||
|
assertEquals("PARAM2", request.getParameter().get(0).getName());
|
||||||
|
assertEquals(Boolean.TRUE, ((Patient) request.getParameter().get(0).getResource()).getActive());
|
||||||
|
assertEquals("PARAM3", request.getParameter().get(1).getName());
|
||||||
|
assertEquals("PARAM3str1", ((StringDt) request.getParameter().get(1).getValue()).getValue());
|
||||||
|
assertEquals("PARAM3", request.getParameter().get(2).getName());
|
||||||
|
assertEquals("PARAM3str2", ((StringDt) request.getParameter().get(2).getValue()).getValue());
|
||||||
|
idx++;
|
||||||
|
|
||||||
|
response = client.opServerListParam(null, Arrays.asList(new StringDt("PARAM3str1"), new StringDt("PARAM3str2")));
|
||||||
|
assertEquals("FOO", response.getParameter().get(0).getName());
|
||||||
|
value = (HttpPost) capt.getAllValues().get(idx);
|
||||||
|
requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent());
|
||||||
|
ourLog.info(requestBody);
|
||||||
|
request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody);
|
||||||
|
assertEquals("http://foo/$OP_SERVER_LIST_PARAM", value.getURI().toASCIIString());
|
||||||
|
assertEquals(2, request.getParameter().size());
|
||||||
|
assertEquals("PARAM3", request.getParameter().get(0).getName());
|
||||||
|
assertEquals("PARAM3str1", ((StringDt) request.getParameter().get(0).getValue()).getValue());
|
||||||
|
assertEquals("PARAM3", request.getParameter().get(1).getName());
|
||||||
|
assertEquals("PARAM3str2", ((StringDt) request.getParameter().get(1).getValue()).getValue());
|
||||||
|
idx++;
|
||||||
|
|
||||||
|
response = client.opServerListParam(null, new ArrayList<StringDt>());
|
||||||
|
assertEquals("FOO", response.getParameter().get(0).getName());
|
||||||
|
value = (HttpPost) capt.getAllValues().get(idx);
|
||||||
|
requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent());
|
||||||
|
ourLog.info(requestBody);
|
||||||
|
request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody);
|
||||||
|
assertEquals("http://foo/$OP_SERVER_LIST_PARAM", value.getURI().toASCIIString());
|
||||||
|
assertEquals(0, request.getParameter().size());
|
||||||
|
idx++;
|
||||||
|
|
||||||
|
response = client.opServerListParam(null, null);
|
||||||
|
assertEquals("FOO", response.getParameter().get(0).getName());
|
||||||
|
value = (HttpPost) capt.getAllValues().get(idx);
|
||||||
|
requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent());
|
||||||
|
ourLog.info(requestBody);
|
||||||
|
request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody);
|
||||||
|
assertEquals("http://foo/$OP_SERVER_LIST_PARAM", value.getURI().toASCIIString());
|
||||||
|
assertEquals(0, request.getParameter().size());
|
||||||
|
idx++;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpType() throws Exception {
|
||||||
|
Parameters outParams = new Parameters();
|
||||||
|
outParams.addParameter().setName("FOO");
|
||||||
|
final String retVal = ourCtx.newXmlParser().encodeResourceToString(outParams);
|
||||||
|
|
||||||
|
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||||
|
when(ourHttpClient.execute(capt.capture())).thenReturn(ourHttpResponse);
|
||||||
|
when(ourHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||||
|
when(ourHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||||
|
when(ourHttpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
|
||||||
|
@Override
|
||||||
|
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||||
|
return new ReaderInputStream(new StringReader(retVal), Charset.forName("UTF-8"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
IOpClient client = ourCtx.newRestfulClient(IOpClient.class, "http://foo");
|
||||||
|
|
||||||
|
int idx = 0;
|
||||||
|
|
||||||
|
Parameters response = client.opType(new StringDt("PARAM1str"), new Patient().setActive(true));
|
||||||
|
assertEquals("FOO", response.getParameter().get(0).getName());
|
||||||
|
HttpPost value = (HttpPost) capt.getAllValues().get(idx);
|
||||||
|
String requestBody = IOUtils.toString(((HttpPost) value).getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(((HttpPost) value).getEntity().getContent());
|
||||||
|
ourLog.info(requestBody);
|
||||||
|
Parameters request = ourCtx.newXmlParser().parseResource(Parameters.class, requestBody);
|
||||||
|
assertEquals("http://foo/Patient/$OP_TYPE", value.getURI().toASCIIString());
|
||||||
|
assertEquals(2, request.getParameter().size());
|
||||||
|
assertEquals("PARAM1", request.getParameter().get(0).getName());
|
||||||
|
assertEquals("PARAM1str", ((StringDt) request.getParameter().get(0).getValue()).getValue());
|
||||||
|
assertEquals("PARAM2", request.getParameter().get(1).getName());
|
||||||
|
assertEquals(Boolean.TRUE, ((Patient) request.getParameter().get(1).getResource()).getActive());
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IOpClient extends IBasicClient {
|
||||||
|
//@formatter:off
|
||||||
|
@Operation(name="$OP_INSTANCE", type=Patient.class)
|
||||||
|
public Parameters opInstance(
|
||||||
|
@IdParam IdDt theId,
|
||||||
|
@OperationParam(name="PARAM1") StringDt theParam1,
|
||||||
|
@OperationParam(name="PARAM2") Patient theParam2
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
@Operation(name="$OP_SERVER")
|
||||||
|
public Parameters opServer(
|
||||||
|
@OperationParam(name="PARAM1") StringDt theParam1,
|
||||||
|
@OperationParam(name="PARAM2") Patient theParam2
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
@Operation(name="$OP_SERVER_LIST_PARAM")
|
||||||
|
public Parameters opServerListParam(
|
||||||
|
@OperationParam(name="PARAM2") Patient theParam2,
|
||||||
|
@OperationParam(name="PARAM3") List<StringDt> theParam3
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
@Operation(name="$OP_TYPE", type=Patient.class)
|
||||||
|
public Parameters opType(
|
||||||
|
@OperationParam(name="PARAM1") StringDt theParam1,
|
||||||
|
@OperationParam(name="PARAM2") Patient theParam2
|
||||||
|
);
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -36,7 +36,7 @@ import ca.uhn.fhir.model.dstu2.resource.Organization;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
import ca.uhn.fhir.rest.annotation.ConditionalOperationParam;
|
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Create;
|
import ca.uhn.fhir.rest.annotation.Create;
|
||||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.OptionalParam;
|
import ca.uhn.fhir.rest.annotation.OptionalParam;
|
||||||
|
@ -188,7 +188,7 @@ public class CreateConditionalTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Create()
|
@Create()
|
||||||
public MethodOutcome createPatient(@ResourceParam Patient thePatient, @ConditionalOperationParam String theConditional, @IdParam IdDt theIdParam) {
|
public MethodOutcome createPatient(@ResourceParam Patient thePatient, @ConditionalUrlParam String theConditional, @IdParam IdDt theIdParam) {
|
||||||
ourLastConditionalUrl = theConditional;
|
ourLastConditionalUrl = theConditional;
|
||||||
ourLastId = thePatient.getId();
|
ourLastId = thePatient.getId();
|
||||||
ourLastIdParam = theIdParam;
|
ourLastIdParam = theIdParam;
|
||||||
|
|
|
@ -21,7 +21,7 @@ import org.junit.Test;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.rest.annotation.ConditionalOperationParam;
|
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Delete;
|
import ca.uhn.fhir.rest.annotation.Delete;
|
||||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
|
@ -114,7 +114,7 @@ public class DeleteConditionalTest {
|
||||||
|
|
||||||
|
|
||||||
@Delete()
|
@Delete()
|
||||||
public MethodOutcome updatePatient(@ConditionalOperationParam String theConditional, @IdParam IdDt theIdParam) {
|
public MethodOutcome updatePatient(@ConditionalUrlParam String theConditional, @IdParam IdDt theIdParam) {
|
||||||
ourLastConditionalUrl = theConditional;
|
ourLastConditionalUrl = theConditional;
|
||||||
ourLastIdParam = theIdParam;
|
ourLastIdParam = theIdParam;
|
||||||
return new MethodOutcome(new IdDt("Patient/001/_history/002"));
|
return new MethodOutcome(new IdDt("Patient/001/_history/002"));
|
||||||
|
|
|
@ -0,0 +1,333 @@
|
||||||
|
package ca.uhn.fhir.rest.server;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
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.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.Parameters;
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||||
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
|
import ca.uhn.fhir.model.primitive.IntegerDt;
|
||||||
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
|
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.util.PortUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by dsotnikov on 2/25/2014.
|
||||||
|
*/
|
||||||
|
public class OperationServerTest {
|
||||||
|
private static CloseableHttpClient ourClient;
|
||||||
|
private static FhirContext ourCtx;
|
||||||
|
|
||||||
|
private static StringDt ourLastParam1;
|
||||||
|
private static Patient ourLastParam2;
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationServerTest.class);
|
||||||
|
private static int ourPort;
|
||||||
|
private static IdDt ourLastId;
|
||||||
|
private static Server ourServer;
|
||||||
|
private static String ourLastMethod;
|
||||||
|
private static List<StringDt> ourLastParam3;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
ourLastParam1 = null;
|
||||||
|
ourLastParam2 = null;
|
||||||
|
ourLastParam3 = null;
|
||||||
|
ourLastId = null;
|
||||||
|
ourLastMethod = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOperationOnType() throws Exception {
|
||||||
|
Parameters p = new Parameters();
|
||||||
|
p.addParameter().setName("PARAM1").setValue(new StringDt("PARAM1val"));
|
||||||
|
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
|
||||||
|
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||||
|
|
||||||
|
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_TYPE");
|
||||||
|
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
|
HttpResponse status = ourClient.execute(httpPost);
|
||||||
|
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
String response = IOUtils.toString(status.getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
|
|
||||||
|
assertEquals("PARAM1val", ourLastParam1.getValue());
|
||||||
|
assertEquals(true, ourLastParam2.getActive().booleanValue());
|
||||||
|
assertEquals("$OP_TYPE", ourLastMethod);
|
||||||
|
|
||||||
|
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
|
||||||
|
assertEquals("RET1", resp.getParameter().get(0).getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOperationOnTypeReturnBundle() throws Exception {
|
||||||
|
Parameters p = new Parameters();
|
||||||
|
p.addParameter().setName("PARAM1").setValue(new StringDt("PARAM1val"));
|
||||||
|
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
|
||||||
|
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||||
|
|
||||||
|
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_TYPE_RET_BUNDLE");
|
||||||
|
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
|
HttpResponse status = ourClient.execute(httpPost);
|
||||||
|
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
String response = IOUtils.toString(status.getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
|
|
||||||
|
assertEquals("PARAM1val", ourLastParam1.getValue());
|
||||||
|
assertEquals(true, ourLastParam2.getActive().booleanValue());
|
||||||
|
assertEquals("$OP_TYPE_RET_BUNDLE", ourLastMethod);
|
||||||
|
|
||||||
|
Bundle resp = ourCtx.newXmlParser().parseResource(Bundle.class, response);
|
||||||
|
assertEquals("100", resp.getEntryFirstRep().getTransactionResponse().getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOperationOnServer() throws Exception {
|
||||||
|
Parameters p = new Parameters();
|
||||||
|
p.addParameter().setName("PARAM1").setValue(new StringDt("PARAM1val"));
|
||||||
|
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
|
||||||
|
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||||
|
|
||||||
|
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/$OP_SERVER");
|
||||||
|
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
|
HttpResponse status = ourClient.execute(httpPost);
|
||||||
|
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
String response = IOUtils.toString(status.getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
|
|
||||||
|
assertEquals("PARAM1val", ourLastParam1.getValue());
|
||||||
|
assertEquals(true, ourLastParam2.getActive().booleanValue());
|
||||||
|
assertEquals("$OP_SERVER", ourLastMethod);
|
||||||
|
|
||||||
|
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
|
||||||
|
assertEquals("RET1", resp.getParameter().get(0).getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOperationWithListParam() throws Exception {
|
||||||
|
Parameters p = new Parameters();
|
||||||
|
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
|
||||||
|
p.addParameter().setName("PARAM3").setValue(new StringDt("PARAM3val1"));
|
||||||
|
p.addParameter().setName("PARAM3").setValue(new StringDt("PARAM3val2"));
|
||||||
|
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||||
|
|
||||||
|
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/$OP_SERVER_LIST_PARAM");
|
||||||
|
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
|
HttpResponse status = ourClient.execute(httpPost);
|
||||||
|
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
String response = IOUtils.toString(status.getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
|
|
||||||
|
assertEquals("$OP_SERVER_LIST_PARAM", ourLastMethod);
|
||||||
|
assertEquals(true, ourLastParam2.getActive().booleanValue());
|
||||||
|
assertEquals(null, ourLastParam1);
|
||||||
|
assertEquals(2, ourLastParam3.size());
|
||||||
|
assertEquals("PARAM3val1", ourLastParam3.get(0).getValue());
|
||||||
|
assertEquals("PARAM3val2", ourLastParam3.get(1).getValue());
|
||||||
|
|
||||||
|
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
|
||||||
|
assertEquals("RET1", resp.getParameter().get(0).getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOperationOnInstance() throws Exception {
|
||||||
|
Parameters p = new Parameters();
|
||||||
|
p.addParameter().setName("PARAM1").setValue(new StringDt("PARAM1val"));
|
||||||
|
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
|
||||||
|
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||||
|
|
||||||
|
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/123/$OP_INSTANCE");
|
||||||
|
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
|
HttpResponse status = ourClient.execute(httpPost);
|
||||||
|
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
String response = IOUtils.toString(status.getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
|
|
||||||
|
assertEquals("PARAM1val", ourLastParam1.getValue());
|
||||||
|
assertEquals(true, ourLastParam2.getActive().booleanValue());
|
||||||
|
assertEquals("123", ourLastId.getIdPart());
|
||||||
|
assertEquals("$OP_INSTANCE", ourLastMethod);
|
||||||
|
|
||||||
|
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
|
||||||
|
assertEquals("RET1", resp.getParameter().get(0).getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOperationWrongParamType() throws Exception {
|
||||||
|
Parameters p = new Parameters();
|
||||||
|
p.addParameter().setName("PARAM1").setValue(new IntegerDt("123"));
|
||||||
|
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
|
||||||
|
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||||
|
|
||||||
|
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_TYPE");
|
||||||
|
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
|
HttpResponse status = ourClient.execute(httpPost);
|
||||||
|
|
||||||
|
assertEquals(400, status.getStatusLine().getStatusCode());
|
||||||
|
String response = IOUtils.toString(status.getEntity().getContent());
|
||||||
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
|
|
||||||
|
ourLog.info(status.getStatusLine().toString());
|
||||||
|
ourLog.info(response);
|
||||||
|
|
||||||
|
assertThat(response, containsString("Request has parameter PARAM1 of type IntegerDt but method expects type StringDt"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void afterClass() throws Exception {
|
||||||
|
ourServer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void beforeClass() throws Exception {
|
||||||
|
ourCtx = FhirContext.forDstu2();
|
||||||
|
ourPort = PortUtil.findFreePort();
|
||||||
|
ourServer = new Server(ourPort);
|
||||||
|
|
||||||
|
ServletHandler proxyHandler = new ServletHandler();
|
||||||
|
RestfulServer servlet = new RestfulServer();
|
||||||
|
servlet.setFhirContext(ourCtx);
|
||||||
|
servlet.setResourceProviders(new PatientProvider());
|
||||||
|
servlet.setPlainProviders(new PlainProvider());
|
||||||
|
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 PlainProvider {
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
@Operation(name="$OP_SERVER")
|
||||||
|
public Parameters opServer(
|
||||||
|
@OperationParam(name="PARAM1") StringDt theParam1,
|
||||||
|
@OperationParam(name="PARAM2") Patient theParam2
|
||||||
|
) {
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
ourLastMethod = "$OP_SERVER";
|
||||||
|
ourLastParam1 = theParam1;
|
||||||
|
ourLastParam2 = theParam2;
|
||||||
|
|
||||||
|
Parameters retVal = new Parameters();
|
||||||
|
retVal.addParameter().setName("RET1").setValue(new StringDt("RETVAL1"));
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
@Operation(name="$OP_SERVER_LIST_PARAM")
|
||||||
|
public Parameters opServerListParam(
|
||||||
|
@OperationParam(name="PARAM2") Patient theParam2,
|
||||||
|
@OperationParam(name="PARAM3") List<StringDt> theParam3
|
||||||
|
) {
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
ourLastMethod = "$OP_SERVER_LIST_PARAM";
|
||||||
|
ourLastParam2 = theParam2;
|
||||||
|
ourLastParam3 = theParam3;
|
||||||
|
|
||||||
|
Parameters retVal = new Parameters();
|
||||||
|
retVal.addParameter().setName("RET1").setValue(new StringDt("RETVAL1"));
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PatientProvider implements IResourceProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends IResource> getResourceType() {
|
||||||
|
return Patient.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
@Operation(name="$OP_TYPE")
|
||||||
|
public Parameters opType(
|
||||||
|
@OperationParam(name="PARAM1") StringDt theParam1,
|
||||||
|
@OperationParam(name="PARAM2") Patient theParam2
|
||||||
|
) {
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
ourLastMethod = "$OP_TYPE";
|
||||||
|
ourLastParam1 = theParam1;
|
||||||
|
ourLastParam2 = theParam2;
|
||||||
|
|
||||||
|
Parameters retVal = new Parameters();
|
||||||
|
retVal.addParameter().setName("RET1").setValue(new StringDt("RETVAL1"));
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
@Operation(name="$OP_TYPE_RET_BUNDLE")
|
||||||
|
public Bundle opTypeRetBundle(
|
||||||
|
@OperationParam(name="PARAM1") StringDt theParam1,
|
||||||
|
@OperationParam(name="PARAM2") Patient theParam2
|
||||||
|
) {
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
ourLastMethod = "$OP_TYPE_RET_BUNDLE";
|
||||||
|
ourLastParam1 = theParam1;
|
||||||
|
ourLastParam2 = theParam2;
|
||||||
|
|
||||||
|
Bundle retVal = new Bundle();
|
||||||
|
retVal.addEntry().getTransactionResponse().setStatus("100");
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
@Operation(name="$OP_INSTANCE")
|
||||||
|
public Parameters opInstance(
|
||||||
|
@IdParam IdDt theId,
|
||||||
|
@OperationParam(name="PARAM1") StringDt theParam1,
|
||||||
|
@OperationParam(name="PARAM2") Patient theParam2
|
||||||
|
) {
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
ourLastMethod = "$OP_INSTANCE";
|
||||||
|
ourLastId = theId;
|
||||||
|
ourLastParam1 = theParam1;
|
||||||
|
ourLastParam2 = theParam2;
|
||||||
|
|
||||||
|
Parameters retVal = new Parameters();
|
||||||
|
retVal.addParameter().setName("RET1").setValue(new StringDt("RETVAL1"));
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package ca.uhn.fhir.rest.server;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
|
||||||
|
import org.hamcrest.core.StringContains;
|
||||||
|
import org.hl7.fhir.instance.model.IBaseResource;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||||
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Operation;
|
||||||
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
|
|
||||||
|
public class ServerInvalidDefinitionDstu2Test {
|
||||||
|
|
||||||
|
private static FhirContext ourCtx = FhirContext.forDstu2();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOperationReturningOldBundleProvider() {
|
||||||
|
RestfulServer srv = new RestfulServer();
|
||||||
|
srv.setFhirContext(ourCtx);
|
||||||
|
srv.setResourceProviders(new OperationReturningOldBundleProvider());
|
||||||
|
|
||||||
|
try {
|
||||||
|
srv.init();
|
||||||
|
fail();
|
||||||
|
} catch (ServletException e) {
|
||||||
|
assertThat(e.getCause().toString(), StringContains.containsString("ConfigurationException"));
|
||||||
|
assertThat(e.getCause().toString(), StringContains.containsString("Can not return a DSTU1 bundle"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class OperationReturningOldBundleProvider implements IResourceProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends IBaseResource> getResourceType() {
|
||||||
|
return Patient.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(name = "$OP_TYPE_RET_OLD_BUNDLE")
|
||||||
|
public ca.uhn.fhir.model.api.Bundle opTypeRetOldBundle(@OperationParam(name = "PARAM1") StringDt theParam1, @OperationParam(name = "PARAM2") Patient theParam2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -35,7 +35,7 @@ import ca.uhn.fhir.model.dstu2.resource.Organization;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
import ca.uhn.fhir.rest.annotation.ConditionalOperationParam;
|
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
|
||||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.OptionalParam;
|
import ca.uhn.fhir.rest.annotation.OptionalParam;
|
||||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||||
|
@ -185,7 +185,7 @@ public class UpdateConditionalTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Update()
|
@Update()
|
||||||
public MethodOutcome updatePatient(@ResourceParam Patient thePatient, @ConditionalOperationParam String theConditional, @IdParam IdDt theIdParam) {
|
public MethodOutcome updatePatient(@ResourceParam Patient thePatient, @ConditionalUrlParam String theConditional, @IdParam IdDt theIdParam) {
|
||||||
ourLastConditionalUrl = theConditional;
|
ourLastConditionalUrl = theConditional;
|
||||||
ourLastId = thePatient.getId();
|
ourLastId = thePatient.getId();
|
||||||
ourLastIdParam = theIdParam;
|
ourLastIdParam = theIdParam;
|
||||||
|
|
|
@ -130,6 +130,11 @@
|
||||||
<action type="add">
|
<action type="add">
|
||||||
Add support for quantity search params in FHIR tester UI
|
Add support for quantity search params in FHIR tester UI
|
||||||
</action>
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
Add support for FHIR "extended operations" as defined in the FHIR DSTU2
|
||||||
|
specification, for the Generic Client, Annotation Client, and
|
||||||
|
Server.
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="0.8" date="2014-Dec-17">
|
<release version="0.8" date="2014-Dec-17">
|
||||||
<action type="add">
|
<action type="add">
|
||||||
|
|
|
@ -82,7 +82,8 @@
|
||||||
<item name="Validation" href="./doc_validation.html" />
|
<item name="Validation" href="./doc_validation.html" />
|
||||||
</item>
|
</item>
|
||||||
<item name="RESTful Client" href="./doc_rest_client.html" >
|
<item name="RESTful Client" href="./doc_rest_client.html" >
|
||||||
<item name="Using RESTful Client" href="./doc_rest_client.html" />
|
<item name="Fluent/Generic Client" href="./doc_rest_client.html" />
|
||||||
|
<item name="Annotation Client" href="./doc_rest_client_annotation.html" />
|
||||||
<item name="Interceptors (client)" href="./doc_rest_client_interceptor.html"/>
|
<item name="Interceptors (client)" href="./doc_rest_client_interceptor.html"/>
|
||||||
</item>
|
</item>
|
||||||
<item name="RESTful Server" href="./doc_rest_server.html" >
|
<item name="RESTful Server" href="./doc_rest_server.html" >
|
||||||
|
|
|
@ -21,17 +21,22 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
There are two types of clients provided by HAPI: Generic and Annotation-driven.
|
There are two types of RESTful clients provided by HAPI:
|
||||||
The generic client (introduced in HAPI-FHIR 0.3) is much simpler to create
|
The Fluent/Generic client (described below) and
|
||||||
|
the <a href="./doc_rest_client_annotation.html">Annotation</a>
|
||||||
|
client.
|
||||||
|
The generic client is simpler to use
|
||||||
and generally provides the faster way to get started. The annotation-driven
|
and generally provides the faster way to get started. The annotation-driven
|
||||||
client can rely on code generation and static binding to specific operations to
|
client relies on static binding to specific operations to
|
||||||
give better compile-time checking against servers with a specific set of capabilities
|
give better compile-time checking against servers with a specific set of capabilities
|
||||||
exposed.
|
exposed. This second model takes more effort to use, but can be useful
|
||||||
|
if the person defining the specific methods to invoke is not the same person
|
||||||
|
who is using those methods.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section name="The Generic Client">
|
<section name="The Fluent/Generic Client">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Creating a generic client simply requires you to create an instance of
|
Creating a generic client simply requires you to create an instance of
|
||||||
|
@ -56,7 +61,7 @@
|
||||||
(although there is no requirement to do so, clients are reusable and thread-safe as well).
|
(although there is no requirement to do so, clients are reusable and thread-safe as well).
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<subsection name="Fluent Operations">
|
<subsection name="Fluent Calls">
|
||||||
<p>
|
<p>
|
||||||
The generic client supports queries using a fluent interface
|
The generic client supports queries using a fluent interface
|
||||||
which is inspired by the fantastic
|
which is inspired by the fantastic
|
||||||
|
@ -373,189 +378,29 @@
|
||||||
value="examples/src/main/java/example/GenericClientExample.java" />
|
value="examples/src/main/java/example/GenericClientExample.java" />
|
||||||
</macro>
|
</macro>
|
||||||
</subsection>
|
</subsection>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section name="The Annotation-Driven Client">
|
<section name="Extended Operations">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
HAPI also provides a second style of client, called the <b>annotation-driven</b> client.
|
In the FHIR DSTU2 version, operations (referred to as "extended operations")
|
||||||
</p>
|
were added. These operations are an RPC style of invocation, with a set of
|
||||||
|
named input parameters passed to the server and a set of named output
|
||||||
<p>
|
parameters returned back.
|
||||||
The design of the annotation-driven client
|
|
||||||
is intended to be similar to that of
|
|
||||||
JAX-WS, so users of that
|
|
||||||
specification should be comfortable with
|
|
||||||
this one. It uses a user-defined interface containing special
|
|
||||||
annotated methods which HAPI binds to calls against a server.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The annotation-driven client is particularly useful if you have a server that
|
|
||||||
exposes a set of specific operations (search parameter combinations, named queries, etc.)
|
|
||||||
and you want to let developers have a stongly/statically typed interface to that
|
|
||||||
server.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
There is no difference in terms of capability between the two styles of
|
To invoke an operation using the client, you simply need to create the
|
||||||
client. There is simply a difference in programming style and complexity. It
|
input
|
||||||
is probably safe to say that the generic client is easier to use and leads to
|
<a href="./apidocs-dstu2/ca/uhn/fhir/model/dstu2/resource/Parameters.html">Parameters</a>
|
||||||
more readable code, at the expense of not giving any visibility into the
|
resource, then pass that to the <code>operation()</code> fluent method.
|
||||||
specific capabilities of the server you are interacting with.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<subsection name="Defining A Restful Client Interface">
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The first step in creating an annotation-driven client is to define a
|
|
||||||
restful client interface.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
A restful client interface class must extend the
|
|
||||||
<a href="./apidocs/ca/uhn/fhir/rest/client/api/IRestfulClient.html">IRestfulClient</a>
|
|
||||||
interface,
|
|
||||||
and will contain one or more methods which have been
|
|
||||||
annotated with special annotations indicating which RESTful
|
|
||||||
operation
|
|
||||||
that method supports. Below is a simple example of a
|
|
||||||
resource provider
|
|
||||||
which supports the
|
|
||||||
<a href="http://hl7.org/implement/standards/fhir/http.html#read">read</a>
|
|
||||||
operation (i.e. retrieve a single resource by ID) as well as the
|
|
||||||
<a href="http://hl7.org/implement/standards/fhir/http.html#search">search</a>
|
|
||||||
operation (i.e. find any resources matching a given criteria) for a
|
|
||||||
specific
|
|
||||||
search criteria.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
You may notice that this interface looks a lot like the Resource
|
|
||||||
Provider
|
|
||||||
which is defined for use by the RESTful server. In fact, it
|
|
||||||
supports all
|
|
||||||
of the same annotations and is essentially identical,
|
|
||||||
other than the
|
|
||||||
fact that for a client you must use an interface but for a server you
|
|
||||||
must use a concrete class with method implementations.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<macro name="snippet">
|
|
||||||
<param name="id" value="provider" />
|
|
||||||
<param name="file"
|
|
||||||
value="examples/src/main/java/example/IRestfulClient.java" />
|
|
||||||
</macro>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
You will probably want to add more methods
|
|
||||||
to your client interface.
|
|
||||||
See
|
|
||||||
<a href="./doc_rest_operations.html">RESTful Operations</a>
|
|
||||||
for
|
|
||||||
lots more examples of how to add methods for various operations.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
</subsection>
|
|
||||||
|
|
||||||
<subsection name="Instantiate the Client">
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Once your client interface is created, all that is left is to
|
|
||||||
create a FhirContext and instantiate the client and you are
|
|
||||||
ready to
|
|
||||||
start using it.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<macro name="snippet">
|
|
||||||
<param name="id" value="client" />
|
|
||||||
<param name="file"
|
|
||||||
value="examples/src/main/java/example/ExampleRestfulClient.java" />
|
|
||||||
</macro>
|
|
||||||
|
|
||||||
</subsection>
|
|
||||||
|
|
||||||
<subsection name="Configuring Encoding (JSON/XML)">
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Restful client interfaces that you create will also extend
|
|
||||||
the interface
|
|
||||||
<a href="./apidocs/ca/uhn/fhir/rest/client/api/IRestfulClient.html">IRestfulClient</a>,
|
|
||||||
which comes with some helpful methods for configuring the way that
|
|
||||||
the client will interact with the server.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
The following snippet shows how to configure the cliet to explicitly
|
|
||||||
request JSON or XML responses, and how to request "pretty printed" responses
|
|
||||||
on servers that support this (HAPI based servers currently).
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<macro name="snippet">
|
|
||||||
<param name="id" value="clientConfig" />
|
|
||||||
<param name="file" value="examples/src/main/java/example/ClientExamples.java" />
|
|
||||||
</macro>
|
|
||||||
|
|
||||||
</subsection>
|
|
||||||
|
|
||||||
<subsection name="A Complete Example">
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The following is a complete example showing a RESTful client
|
|
||||||
using
|
|
||||||
HAPI FHIR.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<macro name="snippet">
|
|
||||||
<param name="id" value="client" />
|
|
||||||
<param name="file"
|
|
||||||
value="examples/src/main/java/example/CompleteExampleClient.java" />
|
|
||||||
</macro>
|
|
||||||
|
|
||||||
</subsection>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section name="Configuring the HTTP Client">
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
RESTful clients (both Generic and Annotation-Driven) use
|
The example below shows a simple operation call.
|
||||||
<a href="http://hc.apache.org/httpcomponents-client-ga/">Apache HTTP Client</a>
|
|
||||||
as a provider. The Apache HTTP Client is very powerful and extremely flexible,
|
|
||||||
but can be confusing at first to configure, because of the low-level approach that
|
|
||||||
the library uses.
|
|
||||||
</p>
|
</p>
|
||||||
|
<macro name="snippet">
|
||||||
<p>
|
<param name="id" value="operation" />
|
||||||
In many cases, the default configuration should suffice. However, if you require anything
|
<param name="file"
|
||||||
more sophisticated (username/password, HTTP proxy settings, etc.) you will need
|
value="examples/src/main/java/example/GenericClientExample.java" />
|
||||||
to configure the underlying client.
|
</macro>
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The underlying client configuration is provided by accessing the
|
|
||||||
<a href="./apidocs/ca/uhn/fhir/rest/client/IRestfulClientFactory.html">IRestfulClientFactory</a>
|
|
||||||
class from the FhirContext.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Note that individual requests and responses
|
|
||||||
can be tweaked using <a href="./doc_rest_client_interceptor.html">Client Interceptors</a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<subsection name="Configuring an HTTP Proxy">
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The following example shows how to configure the use of an HTTP
|
|
||||||
proxy in the client.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<macro name="snippet">
|
|
||||||
<param name="id" value="proxy" />
|
|
||||||
<param name="file" value="examples/src/main/java/example/ClientExamples.java" />
|
|
||||||
</macro>
|
|
||||||
|
|
||||||
</subsection>
|
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -0,0 +1,199 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document xmlns="http://maven.apache.org/XDOC/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<title>Annotation Client</title>
|
||||||
|
<author email="jamesagnew@users.sourceforge.net">James Agnew</author>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<section name="The Annotation-Driven Client">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
HAPI also provides a second style of client, called the <b>annotation-driven</b> client.
|
||||||
|
If you are using the
|
||||||
|
<a href="./doc_rest_client.html">Fluent/Generic Client</a>
|
||||||
|
do not need to read this page.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The design of the annotation-driven client
|
||||||
|
is intended to be similar to that of
|
||||||
|
JAX-WS, so users of that
|
||||||
|
specification should be comfortable with
|
||||||
|
this one. It uses a user-defined interface containing special
|
||||||
|
annotated methods which HAPI binds to calls against a server.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The annotation-driven client is particularly useful if you have a server that
|
||||||
|
exposes a set of specific operations (search parameter combinations, named queries, etc.)
|
||||||
|
and you want to let developers have a stongly/statically typed interface to that
|
||||||
|
server.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
There is no difference in terms of capability between the two styles of
|
||||||
|
client. There is simply a difference in programming style and complexity. It
|
||||||
|
is probably safe to say that the generic client is easier to use and leads to
|
||||||
|
more readable code, at the expense of not giving any visibility into the
|
||||||
|
specific capabilities of the server you are interacting with.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<subsection name="Defining A Restful Client Interface">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The first step in creating an annotation-driven client is to define a
|
||||||
|
restful client interface.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
A restful client interface class must extend the
|
||||||
|
<a href="./apidocs/ca/uhn/fhir/rest/client/api/IRestfulClient.html">IRestfulClient</a>
|
||||||
|
interface,
|
||||||
|
and will contain one or more methods which have been
|
||||||
|
annotated with special annotations indicating which RESTful
|
||||||
|
operation
|
||||||
|
that method supports. Below is a simple example of a
|
||||||
|
resource provider
|
||||||
|
which supports the
|
||||||
|
<a href="http://hl7.org/implement/standards/fhir/http.html#read">read</a>
|
||||||
|
operation (i.e. retrieve a single resource by ID) as well as the
|
||||||
|
<a href="http://hl7.org/implement/standards/fhir/http.html#search">search</a>
|
||||||
|
operation (i.e. find any resources matching a given criteria) for a
|
||||||
|
specific
|
||||||
|
search criteria.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
You may notice that this interface looks a lot like the Resource
|
||||||
|
Provider
|
||||||
|
which is defined for use by the RESTful server. In fact, it
|
||||||
|
supports all
|
||||||
|
of the same annotations and is essentially identical,
|
||||||
|
other than the
|
||||||
|
fact that for a client you must use an interface but for a server you
|
||||||
|
must use a concrete class with method implementations.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<macro name="snippet">
|
||||||
|
<param name="id" value="provider" />
|
||||||
|
<param name="file"
|
||||||
|
value="examples/src/main/java/example/IRestfulClient.java" />
|
||||||
|
</macro>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
You will probably want to add more methods
|
||||||
|
to your client interface.
|
||||||
|
See
|
||||||
|
<a href="./doc_rest_operations.html">RESTful Operations</a>
|
||||||
|
for
|
||||||
|
lots more examples of how to add methods for various operations.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</subsection>
|
||||||
|
|
||||||
|
<subsection name="Instantiate the Client">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Once your client interface is created, all that is left is to
|
||||||
|
create a FhirContext and instantiate the client and you are
|
||||||
|
ready to
|
||||||
|
start using it.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<macro name="snippet">
|
||||||
|
<param name="id" value="client" />
|
||||||
|
<param name="file"
|
||||||
|
value="examples/src/main/java/example/ExampleRestfulClient.java" />
|
||||||
|
</macro>
|
||||||
|
|
||||||
|
</subsection>
|
||||||
|
|
||||||
|
<subsection name="Configuring Encoding (JSON/XML)">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Restful client interfaces that you create will also extend
|
||||||
|
the interface
|
||||||
|
<a href="./apidocs/ca/uhn/fhir/rest/client/api/IRestfulClient.html">IRestfulClient</a>,
|
||||||
|
which comes with some helpful methods for configuring the way that
|
||||||
|
the client will interact with the server.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The following snippet shows how to configure the cliet to explicitly
|
||||||
|
request JSON or XML responses, and how to request "pretty printed" responses
|
||||||
|
on servers that support this (HAPI based servers currently).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<macro name="snippet">
|
||||||
|
<param name="id" value="clientConfig" />
|
||||||
|
<param name="file" value="examples/src/main/java/example/ClientExamples.java" />
|
||||||
|
</macro>
|
||||||
|
|
||||||
|
</subsection>
|
||||||
|
|
||||||
|
<subsection name="A Complete Example">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The following is a complete example showing a RESTful client
|
||||||
|
using
|
||||||
|
HAPI FHIR.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<macro name="snippet">
|
||||||
|
<param name="id" value="client" />
|
||||||
|
<param name="file"
|
||||||
|
value="examples/src/main/java/example/CompleteExampleClient.java" />
|
||||||
|
</macro>
|
||||||
|
|
||||||
|
</subsection>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section name="Configuring the HTTP Client">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
RESTful clients (both Generic and Annotation-Driven) use
|
||||||
|
<a href="http://hc.apache.org/httpcomponents-client-ga/">Apache HTTP Client</a>
|
||||||
|
as a provider. The Apache HTTP Client is very powerful and extremely flexible,
|
||||||
|
but can be confusing at first to configure, because of the low-level approach that
|
||||||
|
the library uses.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
In many cases, the default configuration should suffice. However, if you require anything
|
||||||
|
more sophisticated (username/password, HTTP proxy settings, etc.) you will need
|
||||||
|
to configure the underlying client.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The underlying client configuration is provided by accessing the
|
||||||
|
<a href="./apidocs/ca/uhn/fhir/rest/client/IRestfulClientFactory.html">IRestfulClientFactory</a>
|
||||||
|
class from the FhirContext.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Note that individual requests and responses
|
||||||
|
can be tweaked using <a href="./doc_rest_client_interceptor.html">Client Interceptors</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<subsection name="Configuring an HTTP Proxy">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The following example shows how to configure the use of an HTTP
|
||||||
|
proxy in the client.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<macro name="snippet">
|
||||||
|
<param name="id" value="proxy" />
|
||||||
|
<param name="file" value="examples/src/main/java/example/ClientExamples.java" />
|
||||||
|
</macro>
|
||||||
|
|
||||||
|
</subsection>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</document>
|
|
@ -7,184 +7,20 @@
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<section name="Implementing Resource Provider Operations">
|
|
||||||
|
|
||||||
<!--
|
|
||||||
<p>
|
|
||||||
Jump To...
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="#operations">Operations</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#exceptions">Exceptions</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#tags">Tags</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#compartments">Compartments</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
RESTful Clients and Servers both share the same
|
|
||||||
method pattern, with one key difference: A client
|
|
||||||
is defined using annotated methods on an interface
|
|
||||||
which are used to retrieve
|
|
||||||
resources,
|
|
||||||
whereas a server requires concrete method
|
|
||||||
implementations
|
|
||||||
to actually provide those resources.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Unless otherwise specified, the examples below show
|
|
||||||
<b>server</b>
|
|
||||||
implementations, but client methods will follow the same patterns.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
-->
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section name="Operations">
|
<section name="Operations">
|
||||||
|
|
||||||
<a name="operations" />
|
<a name="operations" />
|
||||||
<p>
|
<p>
|
||||||
The following table lists the operations supported by
|
This page shows the operations which can be implemented on
|
||||||
HAPI FHIR RESTful Servers and Clients.
|
HAPI
|
||||||
|
<a href="./doc_rest_server.html">RESTful Servers</a>, as well as
|
||||||
|
<a href="./doc_rest_client_annotation.html">Annotation Clients</a>.
|
||||||
|
Most of the examples shown here show how to implement a server
|
||||||
|
method, but to perform an equivalent call on an annotation
|
||||||
|
client you simply put a method with the same signature in your
|
||||||
|
client interface.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!--
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr style="font-weight: bold; font-size: 1.2em;">
|
|
||||||
<td>Operation</td>
|
|
||||||
<td>Definition</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="#instance_read">Instance - Read</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
Read the current state of the resource
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="#instance_vread">Instance - VRead</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
Read the state of a specific version of the resource
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="#instance_update">Instance - Update</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
Updates the resource on the server
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="#instance_delete">Instance - Delete</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
Delete a resource
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="#history">Instance - History</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
Retrieve the update history for a particular resource
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="#type_create">Type - Create</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
Create a new resource with a server assigned id
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="#type_search">Type - Search</a>
|
|
||||||
<macro name="toc" >
|
|
||||||
<param name="section" value="8" />
|
|
||||||
<param name="fromDepth" value="2" />
|
|
||||||
</macro>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
Search the resource type based on some filter criteria
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="#history">Type - History</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
Retrieve the update history for a particular resource type
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="#type_validate">Type - Validate</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
Check that the content would be acceptable as an update
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="#system_conformance">System - Conformance</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
Get a conformance statement for the system
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="#system_transaction">System - Transaction</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
Update, create or delete a set of resources as a single transaction
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="#history">System - History</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
Retrieve the update history for all resources
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="#system_search">System - Search</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
Search across all resource types based on some filter criteria
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="#tags">Tag Operations</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
Search across all resource types based on some filter criteria
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<a name="instance_read" />
|
<a name="instance_read" />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
@ -1633,6 +1469,113 @@
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section name="Extended Operations">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
FHIR extended operations are a special type of RPC-style invocation you
|
||||||
|
can perform against a FHIR server, type, or resource instance. These invocations
|
||||||
|
take a
|
||||||
|
<a href="./apidocs-dstu2/ca/uhn/fhir/model/dstu2/resource/Parameters.html">Parameters</a>
|
||||||
|
resource as input, and return either another Parameters resource or a different resource type.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To define an operation, a method should be placed in a
|
||||||
|
<a href="./doc_rest_server.html#resource_providers">Resource Provider</a>
|
||||||
|
class if the operation works against a resource type (e.g. <code>Patient</code>)
|
||||||
|
or a resource instance (e.g. <code>Patient/123</code>), or on a
|
||||||
|
Plain Provider
|
||||||
|
if the operation works against the server (i.e. it is global and not resource specific).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<subsection name="Type-Specific Operations">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To implement a type-specific operation,
|
||||||
|
the method should be annotated with the
|
||||||
|
<code>@Operation</code> tag, and should have an
|
||||||
|
<code>@OperationParam</code> tag for each named parameter that
|
||||||
|
the input Parameters resource may be populated with. The following
|
||||||
|
example shows how to implement the <code>Patient/$everything</code>
|
||||||
|
method, defined in the FHIR specification.
|
||||||
|
</p>
|
||||||
|
<macro name="snippet">
|
||||||
|
<param name="id" value="patientTypeOperation" />
|
||||||
|
<param name="file"
|
||||||
|
value="examples/src/main/java/example/ServerOperations.java" />
|
||||||
|
</macro>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Example URL to invoke this operation (HTTP request body is Parameters resource):
|
||||||
|
<br />
|
||||||
|
<code>POST http://fhir.example.com/Patient/$everything</code>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</subsection>
|
||||||
|
|
||||||
|
<subsection name="Instance-Specific Operations">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To create an instance-specific operation (an operation which takes the
|
||||||
|
ID of a specific resource instance as a part of its request URL),
|
||||||
|
you can add a parameter annotated with the <code>@IdParam</code> annotation,
|
||||||
|
of type <code>IdDt</code>. The following example show how to implement the
|
||||||
|
<code>Patient/[id]/$everything</code> operation.
|
||||||
|
</p>
|
||||||
|
<macro name="snippet">
|
||||||
|
<param name="id" value="patientInstanceOperation" />
|
||||||
|
<param name="file"
|
||||||
|
value="examples/src/main/java/example/ServerOperations.java" />
|
||||||
|
</macro>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Example URL to invoke this operation (HTTP request body is Parameters resource):
|
||||||
|
<br />
|
||||||
|
<code>http://fhir.example.com/Patient/123/$everything</code>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</subsection>
|
||||||
|
|
||||||
|
<subsection name="Server Operations">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Server operations do not operate on a specific resource type or
|
||||||
|
instance, but rather operate globally on the server itself. The following
|
||||||
|
example show how to implement the
|
||||||
|
<code>$closure</code> operation. Note that the <code>concept</code> parameter
|
||||||
|
in the example has a cardinality of <code>0..*</code> according to the
|
||||||
|
FHIR specification, so a <code>List<Coding></code>
|
||||||
|
is used as the parameter type.
|
||||||
|
</p>
|
||||||
|
<macro name="snippet">
|
||||||
|
<param name="id" value="serverOperation" />
|
||||||
|
<param name="file"
|
||||||
|
value="examples/src/main/java/example/ServerOperations.java" />
|
||||||
|
</macro>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Example URL to invoke this operation (HTTP request body is Parameters resource):
|
||||||
|
<br />
|
||||||
|
<code>http://fhir.example.com/$closure</code>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</subsection>
|
||||||
|
|
||||||
|
<subsection name="Returning Multiple OUT Parameters">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
In all of the Extended Operation examples above, the return
|
||||||
|
type specified for the operation is a single Resource instance. This is
|
||||||
|
a common pattern in FHIR defined operations. However, it is also
|
||||||
|
possible for an extended operation to be defined with multiple
|
||||||
|
and/or repeating OUT parameters. In this case, you can return
|
||||||
|
a <code>Parameters</code> resource directly.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</subsection>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</document>
|
</document>
|
||||||
|
|
|
@ -31,7 +31,8 @@
|
||||||
|
|
||||||
<h4>RESTful Client</h4>
|
<h4>RESTful Client</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="./doc_rest_client.html">Using RESTful Client</a></li>
|
<li><a href="./doc_rest_client.html">Fluent/Generic Client</a></li>
|
||||||
|
<li><a href="./doc_rest_client_annotation.html">Annotation Client</a></li>
|
||||||
<li><a href="./doc_rest_client_interceptor.html">Interceptors (client)</a></li>
|
<li><a href="./doc_rest_client_interceptor.html">Interceptors (client)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue