Work on support for $everything operation

This commit is contained in:
James Agnew 2015-03-23 19:06:14 -04:00
parent c8a70c1904
commit cffda7539f
20 changed files with 794 additions and 107 deletions

View File

@ -364,6 +364,29 @@ public class GenericClientExample {
public static void main(String[] args) {
operation();
}
@SuppressWarnings("unused")
private static void operationHttpGet() {
// START SNIPPET: operationHttpGet
// Create a client to talk to the HeathIntersections server
FhirContext ctx = FhirContext.forDstu2();
IGenericClient client = ctx.newRestfulGenericClient("http://fhir-dev.healthintersections.com.au/open");
client.registerInterceptor(new LoggingInterceptor(true));
// Create the input parameters to pass to the server
Parameters inParams = new Parameters();
inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01"));
inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01"));
// Invoke $everything on "Patient/1"
Parameters outParams = client
.operation()
.onInstance(new IdDt("Patient", "1"))
.named("$everything")
.withParameters(inParams)
.useHttpGet() // Use HTTP GET instead of POST
.execute();
// END SNIPPET: operationHttpGet
}
@SuppressWarnings("unused")
private static void operation() {
@ -388,4 +411,22 @@ public class GenericClientExample {
// END SNIPPET: operation
}
@SuppressWarnings("unused")
private static void operationNoIn() {
// START SNIPPET: operationNoIn
// Create a client to talk to the HeathIntersections server
FhirContext ctx = FhirContext.forDstu2();
IGenericClient client = ctx.newRestfulGenericClient("http://fhir-dev.healthintersections.com.au/open");
client.registerInterceptor(new LoggingInterceptor(true));
// Invoke $everything on "Patient/1"
Parameters outParams = client
.operation()
.onInstance(new IdDt("Patient", "1"))
.named("$everything")
.withNoParameters(Parameters.class) // No input parameters
.execute();
// END SNIPPET: operationNoIn
}
}

View File

@ -1030,6 +1030,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
private String myOperationName;
private IBaseParameters myParameters;
private Class<? extends IBaseResource> myType;
private boolean myUseHttpGet;
@SuppressWarnings("unchecked")
@Override
@ -1047,7 +1048,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
id = null;
}
HttpPostClientInvocation invocation = OperationMethodBinding.createOperationInvocation(myContext, resourceName, id, myOperationName, myParameters);
BaseHttpClientInvocation invocation = OperationMethodBinding.createOperationInvocation(myContext, resourceName, id, myOperationName, myParameters, myUseHttpGet);
IClientResponseHandler handler;
handler = new ResourceResponseHandler(myParameters.getClass(), null);
@ -1103,6 +1104,27 @@ public class GenericClient extends BaseClient implements IGenericClient {
return this;
}
@SuppressWarnings("unchecked")
@Override
public <T extends IBaseParameters> IOperationUntypedWithInput<T> withNoParameters(Class<T> theOutputParameterType) {
Validate.notNull(theOutputParameterType, "theOutputParameterType may not be null");
RuntimeResourceDefinition def = myContext.getResourceDefinition(theOutputParameterType);
if (def == null) {
throw new IllegalArgumentException("theOutputParameterType must refer to a HAPI FHIR Resource type: " + theOutputParameterType.getName());
}
if (!"Parameters".equals(def.getName())) {
throw new IllegalArgumentException("theOutputParameterType must refer to a HAPI FHIR Resource type for a resource named " + "Parameters" + " - " + theOutputParameterType.getName() + " is a resource named: " + def.getName());
}
myParameters = (IBaseParameters) def.newInstance();
return this;
}
@Override
public IOperationUntypedWithInput useHttpGet() {
myUseHttpGet = true;
return this;
}
}
private final class OperationOutcomeResponseHandler implements IClientResponseHandler<BaseOperationOutcome> {

View File

@ -24,6 +24,20 @@ import org.hl7.fhir.instance.model.api.IBaseParameters;
public interface IOperationUntyped {
/**
* Use the given parameters resource as the input to the operation
*
* @param theParameters The parameters to use as input. May also be <code>null</code> if the operation
* does not require any input parameters.
*/
<T extends IBaseParameters> IOperationUntypedWithInput<T> withParameters(T theParameters);
/**
* The operation does not require any input parameters
*
* @param theOutputParameterType The type to use for the output parameters (this should be set to
* <code>Parameters.class</code> drawn from the version of the FHIR structures you are using)
*/
<T extends IBaseParameters> IOperationUntypedWithInput<T> withNoParameters(Class<T> theOutputParameterType);
}

View File

@ -24,4 +24,15 @@ import org.hl7.fhir.instance.model.api.IBaseParameters;
public interface IOperationUntypedWithInput<T extends IBaseParameters> extends IClientExecutable<IOperationUntypedWithInput<T>, T> {
/**
* The client should invoke this method using an HTTP GET instead of an HTTP POST. Note that
* according the the FHIR specification, all methods must support using the POST method, but
* only certain methods may support the HTTP GET method, so it is generally not necessary
* to use this feature.
* <p>
* If you have a specific reason for needing to use a GET however, this method will enable it.
* </p>
*/
IOperationUntypedWithInput<T> useHttpGet();
}

View File

@ -20,15 +20,22 @@ package ca.uhn.fhir.rest.method;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.BufferedReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.http.MethodNotSupportedException;
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 org.hl7.fhir.instance.model.api.IBaseParameters;
import ca.uhn.fhir.context.ConfigurationException;
@ -38,6 +45,7 @@ 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.primitive.StringDt;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.Operation;
@ -48,44 +56,57 @@ import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.RestfulServerUtils;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.util.FhirTerser;
public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
private Integer myIdParamIndex;
private String myName;
private boolean myHttpGetPermitted;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationMethodBinding.class);
private final boolean myHttpGetPermitted;
private final Integer myIdParamIndex;
private final String myName;
private final ReturnTypeEnum myReturnType;
public OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider, Operation theAnnotation) {
public OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
Operation theAnnotation) {
super(theReturnResourceType, theMethod, theContext, theProvider);
myHttpGetPermitted = theAnnotation.idempotent();
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");
String name = theAnnotation.name();
if (isBlank(name)) {
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 (name.startsWith("$") == false) {
name = "$" + name;
}
myName = name;
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) {
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());
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());
}
if (theMethod.getReturnType().equals(IBundleProvider.class)) {
myReturnType = ReturnTypeEnum.BUNDLE;
} else {
myReturnType = ReturnTypeEnum.RESOURCE;
}
}
@ -102,7 +123,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
@Override
public ReturnTypeEnum getReturnType() {
return ReturnTypeEnum.RESOURCE;
return myReturnType;
}
@Override
@ -128,20 +149,6 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
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();
if (theRequest.getRequestType() == RequestTypeEnum.GET) {
return null;
}
Class<? extends IBaseResource> wantedResourceType = getContext().getResourceDefinition("Parameters").getImplementingClass();
return parser.parseResource(wantedResourceType, requestReader);
}
@Override
public BaseHttpClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
String id = null;
@ -158,7 +165,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
}
}
return createOperationInvocation(getContext(), getResourceName(), id, myName, parameters);
return createOperationInvocation(getContext(), getResourceName(), id, myName, parameters, false);
}
@Override
@ -175,11 +182,12 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
String message = getContext().getLocalizer().getMessage(OperationMethodBinding.class, "methodNotSupported", theRequest.getRequestType(), RequestTypeEnum.POST.name());
throw new MethodNotAllowedException(message, RequestTypeEnum.POST);
} else {
String message = getContext().getLocalizer().getMessage(OperationMethodBinding.class, "methodNotSupported", theRequest.getRequestType(), RequestTypeEnum.GET.name(), RequestTypeEnum.POST.name());
throw new MethodNotAllowedException(message, RequestTypeEnum.GET, RequestTypeEnum.POST);
String message = getContext().getLocalizer().getMessage(OperationMethodBinding.class, "methodNotSupported", theRequest.getRequestType(), RequestTypeEnum.GET.name(),
RequestTypeEnum.POST.name());
throw new MethodNotAllowedException(message, RequestTypeEnum.GET, RequestTypeEnum.POST);
}
}
if (myIdParamIndex != null) {
theMethodParams[myIdParamIndex] = theRequest.getId();
}
@ -189,7 +197,22 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
return retVal;
}
public static HttpPostClientInvocation createOperationInvocation(FhirContext theContext, String theResourceName, String theId, String theOperationName, IBaseParameters theInput) {
@Override
protected Object parseRequestObject(Request theRequest) throws IOException {
EncodingEnum encoding = RestfulServerUtils.determineRequestEncoding(theRequest);
IParser parser = encoding.newParser(getContext());
BufferedReader requestReader = theRequest.getServletRequest().getReader();
if (theRequest.getRequestType() == RequestTypeEnum.GET) {
return null;
}
Class<? extends IBaseResource> wantedResourceType = getContext().getResourceDefinition("Parameters").getImplementingClass();
return parser.parseResource(wantedResourceType, requestReader);
}
public static BaseHttpClientInvocation createOperationInvocation(FhirContext theContext, String theResourceName, String theId, String theOperationName, IBaseParameters theInput,
boolean theUseHttpGet) {
StringBuilder b = new StringBuilder();
if (theResourceName != null) {
b.append(theResourceName);
@ -206,7 +229,35 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
}
b.append(theOperationName);
return new HttpPostClientInvocation(theContext, theInput, b.toString());
}
if (!theUseHttpGet) {
return new HttpPostClientInvocation(theContext, theInput, b.toString());
} else {
FhirTerser t = theContext.newTerser();
List<Object> parameters = t.getValues(theInput, "Parameters.parameter");
Map<String, List<String>> params = new HashMap<String, List<String>>();
for (Object nextParameter : parameters) {
StringDt nextNameDt = (StringDt) t.getSingleValueOrNull((IBase) nextParameter, "name");
if (nextNameDt == null || nextNameDt.isEmpty()) {
ourLog.warn("Ignoring input parameter with no value in Parameters.parameter.name in operation client invocation");
continue;
}
String nextName = nextNameDt.getValue();
if (!params.containsKey(nextName)) {
params.put(nextName, new ArrayList<String>());
}
IBaseDatatype value = (IBaseDatatype) t.getSingleValueOrNull((IBase) nextParameter, "value[x]");
if (value == null) {
continue;
}
if (!(value instanceof IPrimitiveType)) {
throw new IllegalArgumentException("Can not invoke operation as HTTP GET when it has parameters with a composite (non priitive) datatype as the value. Found value: " + value.getClass().getName());
}
IPrimitiveType<?> primitive = (IPrimitiveType<?>) value;
params.get(nextName).add(primitive.getValueAsString());
}
return new HttpGetClientInvocation(params, b.toString());
}
}
}

View File

@ -54,14 +54,16 @@ public class FifoMemoryPagingProvider implements IPagingProvider {
return myBundleProviders.get(theId);
}
public void setDefaultPageSize(int theDefaultPageSize) {
public FifoMemoryPagingProvider setDefaultPageSize(int theDefaultPageSize) {
Validate.isTrue(theDefaultPageSize > 0, "size must be greater than 0");
myDefaultPageSize = theDefaultPageSize;
return this;
}
public void setMaximumPageSize(int theMaximumPageSize) {
public FifoMemoryPagingProvider setMaximumPageSize(int theMaximumPageSize) {
Validate.isTrue(theMaximumPageSize > 0, "size must be greater than 0");
myMaximumPageSize = theMaximumPageSize;
return this;
}
@Override

View File

@ -26,6 +26,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.IBase;
import org.hl7.fhir.instance.model.IBaseResource;
@ -204,7 +205,7 @@ public class FhirTerser {
return retVal;
}
public List<Object> getValues(IResource theResource, String thePath) {
public List<Object> getValues(IBaseResource theResource, String thePath) {
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
BaseRuntimeElementCompositeDefinition<?> currentDef = def;
@ -323,4 +324,25 @@ public class FhirTerser {
visit(theResource, null, def, theVisitor);
}
public Object getSingleValueOrNull(IBase theTarget, String thePath) {
Validate.notNull(theTarget, "theTarget must not be null");
Validate.notBlank(thePath, "thePath must not be empty");
BaseRuntimeElementDefinition<?> def = myContext.getElementDefinition(theTarget.getClass());
if (!(def instanceof BaseRuntimeElementCompositeDefinition)) {
throw new IllegalArgumentException("Target is not a composite type: " + theTarget.getClass().getName());
}
BaseRuntimeElementCompositeDefinition<?> currentDef = (BaseRuntimeElementCompositeDefinition<?>) def;
Object currentObj = theTarget;
List<String> parts = Arrays.asList(thePath.split("\\."));
List<Object> retVal = getValues(currentDef, currentObj, parts);
if (retVal.isEmpty()) {
return null;
} else {
return retVal.get(0);
}
}
}

View File

@ -1246,8 +1246,8 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
entity.getTags().remove(next);
}
}
//@formatter:off
//@formatter:on
if (entity.getTags().isEmpty()) {
entity.setHasTags(false);
}
@ -1318,21 +1318,26 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
ourLog.info("Sort PID order is now: {}", loadPids);
pids = new ArrayList<Long>(loadPids);
// Any ressources which weren't matched by the sort get added to the bottom
for (Long next : originalPids) {
if (loadPids.contains(next)==false) {
if (loadPids.contains(next) == false) {
pids.add(next);
}
}
}else {
} else {
pids = new ArrayList<Long>(loadPids);
}
} else {
pids = new ArrayList<Long>(loadPids);
}
// Load _revinclude resources
if (theParams.getRevIncludes() != null && theParams.getRevIncludes().isEmpty() == false) {
loadReverseIncludes(pids, theParams.getRevIncludes());
}
IBundleProvider retVal = new IBundleProvider() {
@Override
@ -1352,12 +1357,11 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
List<IResource> retVal = new ArrayList<IResource>();
loadResourcesByPid(pidsSubList, retVal, BundleEntrySearchModeEnum.MATCH);
// Load _revinclude resources
if (theParams.getRevIncludes() != null && theParams.getRevIncludes().isEmpty()==false) {
loadReverseIncludes(pidsSubList, retVal, theParams.getRevIncludes());
}
// Load _include resources
/*
* Load _include resources - Note that _revincludes are handled differently
* than _include ones, as they are counted towards the total count and paged,
* so they are loaded outside the bundle provider
*/
if (theParams.getIncludes() != null && theParams.getIncludes().isEmpty() == false) {
Set<IdDt> previouslyLoadedPids = new HashSet<IdDt>();
@ -1401,7 +1405,8 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
if (previouslyLoadedPids.size() >= getConfig().getIncludeLimit()) {
OperationOutcome oo = new OperationOutcome();
oo.addIssue().setSeverity(IssueSeverityEnum.WARNING).setDetails("Not all _include resources were actually included as the request surpassed the limit of " + getConfig().getIncludeLimit() + " resources");
oo.addIssue().setSeverity(IssueSeverityEnum.WARNING)
.setDetails("Not all _include resources were actually included as the request surpassed the limit of " + getConfig().getIncludeLimit() + " resources");
retVal.add(0, oo);
}
}
@ -1435,57 +1440,65 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
return resources;
}
protected void loadReverseIncludes(List<Long> theMatches, List<IResource> theResourceListToPopulate, Set<Include> theRevIncludes) {
protected void loadReverseIncludes(List<Long> theMatches, Set<Include> theRevIncludes) {
if (theMatches.size() == 0) {
return;
}
Set<Long> pidsToInclude = new HashSet<Long>();
HashSet<Long> pidsToInclude = new HashSet<Long>();
for (Include nextInclude : theRevIncludes) {
int colonIdx = nextInclude.getValue().indexOf(':');
if (colonIdx < 2) {
continue;
}
String resType = nextInclude.getValue().substring(0, colonIdx);
RuntimeResourceDefinition def = getContext().getResourceDefinition(resType);
if (def == null) {
ourLog.warn("Unknown resource type in _revinclude=" + nextInclude.getValue());
continue;
}
String paramName = nextInclude.getValue().substring(colonIdx+1);
RuntimeSearchParam param = def.getSearchParam(paramName);
if (param == null) {
ourLog.warn("Unknown param name in _revinclude=" + nextInclude.getValue());
continue;
}
for (String nextPath : param.getPathsSplit()) {
String sql = "SELECT r FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r.myTargetResourcePid IN (:target_pids)";
boolean matchAll = "*".equals(nextInclude.getValue());
if (matchAll) {
String sql = "SELECT r FROM ResourceLink r WHERE r.myTargetResourcePid IN (:target_pids)";
TypedQuery<ResourceLink> q = myEntityManager.createQuery(sql, ResourceLink.class);
q.setParameter("src_path", nextPath);
q.setParameter("target_pids", theMatches);
List<ResourceLink> results = q.getResultList();
for (ResourceLink resourceLink : results) {
pidsToInclude.add(resourceLink.getSourceResourcePid());
}
} else {
int colonIdx = nextInclude.getValue().indexOf(':');
if (colonIdx < 2) {
continue;
}
String resType = nextInclude.getValue().substring(0, colonIdx);
RuntimeResourceDefinition def = getContext().getResourceDefinition(resType);
if (def == null) {
ourLog.warn("Unknown resource type in _revinclude=" + nextInclude.getValue());
continue;
}
String paramName = nextInclude.getValue().substring(colonIdx + 1);
RuntimeSearchParam param = def.getSearchParam(paramName);
if (param == null) {
ourLog.warn("Unknown param name in _revinclude=" + nextInclude.getValue());
continue;
}
for (String nextPath : param.getPathsSplit()) {
String sql = "SELECT r FROM ResourceLink r WHERE r.mySourcePath = :src_path AND r.myTargetResourcePid IN (:target_pids)";
TypedQuery<ResourceLink> q = myEntityManager.createQuery(sql, ResourceLink.class);
q.setParameter("src_path", nextPath);
q.setParameter("target_pids", theMatches);
List<ResourceLink> results = q.getResultList();
for (ResourceLink resourceLink : results) {
pidsToInclude.add(resourceLink.getSourceResourcePid());
}
}
}
loadResourcesByPid(pidsToInclude, theResourceListToPopulate, BundleEntrySearchModeEnum.INCLUDE);
}
theMatches.addAll(pidsToInclude);
}
@Override
public IBundleProvider search(String theParameterName, IQueryParameterType theValue) {
return search(Collections.singletonMap(theParameterName, theValue));
}
protected abstract List<Object> getIncludeValues(FhirTerser theTerser, Include theInclude, IResource theResource, RuntimeResourceDefinition theResourceDef);
@Override
public Set<Long> searchForIds(Map<String, IQueryParameterType> theParams) {
SearchParameterMap map = new SearchParameterMap();
@ -1632,8 +1645,7 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
}
/**
* If set, the given param will be treated as a secondary primary key, and multiple resources will not be able to
* share the same value.
* If set, the given param will be treated as a secondary primary key, and multiple resources will not be able to share the same value.
*/
public void setSecondaryPrimaryKeyParamName(String theSecondaryPrimaryKeyParamName) {
mySecondaryPrimaryKeyParamName = theSecondaryPrimaryKeyParamName;
@ -1760,7 +1772,8 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
private void validateResourceType(BaseHasResource entity) {
if (!myResourceName.equals(entity.getResourceType())) {
throw new ResourceNotFoundException("Resource with ID " + entity.getIdDt().getIdPart() + " exists but it is not of type " + myResourceName + ", found resource of type " + entity.getResourceType());
throw new ResourceNotFoundException("Resource with ID " + entity.getIdDt().getIdPart() + " exists but it is not of type " + myResourceName + ", found resource of type "
+ entity.getResourceType());
}
}
@ -1785,7 +1798,7 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
@Override
public MetaDt metaGetOperation(IdDt theId) {
Long pid = super.translateForcedIdToPid(theId);
String sql = "SELECT d FROM TagDefinition d WHERE d.myId IN (SELECT DISTINCT t.myTagId FROM ResourceTag t WHERE t.myResourceType = :res_type AND t.myResourceId = :res_id)";
TypedQuery<TagDefinition> q = myEntityManager.createQuery(sql, TagDefinition.class);
q.setParameter("res_type", myResourceName);
@ -1818,8 +1831,8 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
}
}
}
//@formatter:off
//@formatter:on
if (entity.getTags().isEmpty()) {
entity.setHasTags(false);
}
@ -1827,7 +1840,7 @@ public abstract class BaseFhirResourceDao<T extends IResource> extends BaseFhirD
myEntityManager.merge(entity);
ourLog.info("Processed metaDeleteOperation on {} in {}ms", new Object[] { theResourceId.getValue(), w.getMillisAndRestart() });
return metaGetOperation(theResourceId);
}

View File

@ -0,0 +1,19 @@
package ca.uhn.fhir.jpa.provider;
import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.dstu2.resource.Encounter;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.IBundleProvider;
public class BaseJpaResourceProviderEncounterDstu2 extends JpaResourceProviderDstu2<Encounter> {
}

View File

@ -0,0 +1,33 @@
package ca.uhn.fhir.jpa.provider;
import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.IBundleProvider;
public class BaseJpaResourceProviderPatientDstu2 extends JpaResourceProviderDstu2<Patient> {
@Operation(name="$everything", idempotent=true)
public IBundleProvider everything(HttpServletRequest theServletRequest, @IdParam IdDt theId) {
startRequest(theServletRequest);
try {
SearchParameterMap paramMap = new SearchParameterMap();
paramMap.setRevIncludes(Collections.singleton(new Include("*")));
paramMap.setIncludes(Collections.singleton(new Include("*")));
paramMap.add("_id", new StringParam(theId.getIdPart()));
ca.uhn.fhir.rest.server.IBundleProvider retVal = getDao().search(paramMap);
return retVal;
} finally {
endRequest(theServletRequest);
}
}
}

View File

@ -107,7 +107,7 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
parameters.addParameter().setName("return").setValue(getDao().metaGetOperation());
return parameters;
}
//@formatter:off
@Operation(name="$meta", idempotent=true, returnParameters= {
@OperationParam(name="return", type=MetaDt.class)
@ -118,13 +118,13 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
parameters.addParameter().setName("return").setValue(getDao().metaGetOperation(theId));
return parameters;
}
//@formatter:off
@Operation(name="$meta-add", idempotent=true, returnParameters= {
@OperationParam(name="return", type=MetaDt.class)
})
//@formatter:on
public Parameters metaAdd(@IdParam IdDt theId, @OperationParam(name="meta") MetaDt theMeta) {
public Parameters metaAdd(@IdParam IdDt theId, @OperationParam(name = "meta") MetaDt theMeta) {
Parameters parameters = new Parameters();
parameters.addParameter().setName("return").setValue(getDao().metaAddOperation(theId, theMeta));
return parameters;
@ -135,7 +135,7 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
@OperationParam(name="return", type=MetaDt.class)
})
//@formatter:on
public Parameters metaDelete(@IdParam IdDt theId, @OperationParam(name="meta") MetaDt theMeta) {
public Parameters metaDelete(@IdParam IdDt theId, @OperationParam(name = "meta") MetaDt theMeta) {
Parameters parameters = new Parameters();
parameters.addParameter().setName("return").setValue(getDao().metaDeleteOperation(theId, theMeta));
return parameters;

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.provider;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.greaterThan;
@ -10,7 +11,9 @@ import static org.junit.Assert.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
@ -37,18 +40,23 @@ import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.dstu.resource.Device;
import ca.uhn.fhir.model.dstu.resource.Practitioner;
import ca.uhn.fhir.model.dstu2.composite.PeriodDt;
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
import ca.uhn.fhir.model.dstu2.resource.DiagnosticOrder;
import ca.uhn.fhir.model.dstu2.resource.DocumentManifest;
import ca.uhn.fhir.model.dstu2.resource.DocumentReference;
import ca.uhn.fhir.model.dstu2.resource.Encounter;
import ca.uhn.fhir.model.dstu2.resource.ImagingStudy;
import ca.uhn.fhir.model.dstu2.resource.Location;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.Organization;
import ca.uhn.fhir.model.dstu2.resource.Parameters;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.valueset.EncounterClassEnum;
import ca.uhn.fhir.model.dstu2.valueset.EncounterStateEnum;
@ -298,6 +306,53 @@ public class ResourceProviderDstu2Test {
assertEquals(BundleEntrySearchModeEnum.INCLUDE, found.getEntries().get(1).getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE));
}
@Test
public void testEverythingOperation() throws Exception {
String methodName = "testEverythingOperation";
Organization org1 = new Organization();
org1.setName(methodName + "1");
IdDt orgId1 = ourClient.create().resource(org1).execute().getId();
Patient p = new Patient();
p.addName().addFamily(methodName);
p.getManagingOrganization().setReference(orgId1);
IdDt patientId = ourClient.create().resource(p).execute().getId();
Organization org2 = new Organization();
org2.setName(methodName + "1");
IdDt orgId2 = ourClient.create().resource(org2).execute().getId();
Device dev = new Device();
dev.setModel(methodName);
dev.getOwner().setReference(orgId2);
IdDt devId = ourClient.create().resource(dev).execute().getId();
Observation obs = new Observation();
obs.getSubject().setReference(patientId);
obs.getDevice().setReference(devId);
IdDt obsId = ourClient.create().resource(obs).execute().getId();
Encounter enc = new Encounter();
enc.getPatient().setReference(patientId);
IdDt encId = ourClient.create().resource(enc).execute().getId();
Parameters output = ourClient.operation().onInstance(patientId).named("everything").withNoParameters(Parameters.class).execute();
ca.uhn.fhir.model.dstu2.resource.Bundle b = (ca.uhn.fhir.model.dstu2.resource.Bundle) output.getParameterFirstRep().getResource();
Set<IdDt> ids = new HashSet<IdDt>();
for (Entry next : b.getEntry()) {
ids.add(next.getResource().getId());
}
ourLog.info(ids.toString());
assertThat(ids, containsInAnyOrder(patientId, devId, obsId, encId, orgId1, orgId2));
// _revinclude's are counted but not _include's
assertEquals(3, b.getTotal().intValue());
}
@Test
public void testCountParam() throws Exception {
@ -680,7 +735,7 @@ public class ResourceProviderDstu2Test {
ourServer.setHandler(proxyHandler);
ourServer.start();
ourFhirCtx.getRestfulClientFactory().setSocketTimeout(600 * 1000);
ourFhirCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
ourClient = ourFhirCtx.newRestfulGenericClient(ourServerBase);
ourClient.registerInterceptor(new LoggingInterceptor(true));

View File

@ -121,7 +121,7 @@ public class IdDtTest {
ResourceReferenceDt ref = actual.getManagingOrganization();
assertEquals("Organization", ref.getReference().getResourceType());
assertEquals("123", ref.getReference().getIdPart());
assertEquals("999", ref.getReference().getVersionIdPart());
assertEquals(null, ref.getReference().getVersionIdPart());
}
@ -145,7 +145,7 @@ public class IdDtTest {
ResourceReferenceDt ref = actual.getManagingOrganization();
assertEquals(null, ref.getReference().getResourceType());
assertEquals("123", ref.getReference().getIdPart());
assertEquals("999", ref.getReference().getVersionIdPart());
assertEquals(null, ref.getReference().getVersionIdPart());
}

View File

@ -99,7 +99,7 @@ public class ReferenceParameterTest {
assertThat(status.getFirstHeader("Content-Location").getValue(), containsString("Patient/22/_history/33"));
assertEquals("44", p.getManagingOrganization().getReference().getIdPart());
assertEquals("55", p.getManagingOrganization().getReference().getVersionIdPart());
assertEquals(null, p.getManagingOrganization().getReference().getVersionIdPart());
}
@Test
@ -119,7 +119,7 @@ public class ReferenceParameterTest {
assertEquals("33", p.getId().getVersionIdPart());
assertEquals("44", p.getManagingOrganization().getReference().getIdPart());
assertEquals("55", p.getManagingOrganization().getReference().getVersionIdPart());
assertEquals(null, p.getManagingOrganization().getReference().getVersionIdPart());
}
@Test

View File

@ -36,6 +36,7 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.resource.Conformance;
import ca.uhn.fhir.model.dstu2.resource.OperationDefinition;
import ca.uhn.fhir.model.dstu2.resource.Conformance.Rest;
import ca.uhn.fhir.model.dstu2.resource.Conformance.RestResource;
import ca.uhn.fhir.model.dstu2.resource.Conformance.RestResourceInteraction;
@ -50,6 +51,7 @@ import ca.uhn.fhir.rest.annotation.Metadata;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.DynamicSearchMethodBinding;
import ca.uhn.fhir.rest.method.IParameter;
import ca.uhn.fhir.rest.method.OperationMethodBinding;
import ca.uhn.fhir.rest.method.SearchMethodBinding;
import ca.uhn.fhir.rest.method.SearchParameter;
import ca.uhn.fhir.rest.server.Constants;
@ -166,6 +168,10 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
handleSearchMethodBinding(rest, resource, resourceName, def, includes, (SearchMethodBinding) nextMethodBinding);
} else if (nextMethodBinding instanceof DynamicSearchMethodBinding) {
handleDynamicSearchMethodBinding(resource, def, includes, (DynamicSearchMethodBinding) nextMethodBinding);
} else if (nextMethodBinding instanceof OperationMethodBinding) {
OperationMethodBinding methodBinding = (OperationMethodBinding)nextMethodBinding;
OperationDefinition op = new OperationDefinition();
// op.
}
Collections.sort(resource.getInteraction(), new Comparator<RestResourceInteraction>() {

View File

@ -269,6 +269,253 @@ public class GenericClientTestDstu2 {
idx++;
}
@Test
public void testOperationWithNoInParameters() throws Exception {
IParser p = ourCtx.newXmlParser();
Parameters inParams = new Parameters();
final String reqString = p.encodeResourceToString(inParams);
Parameters outParams = new Parameters();
outParams.addParameter().setValue(new StringDt("STRINGVALOUT1"));
outParams.addParameter().setValue(new StringDt("STRINGVALOUT2"));
final String respString = p.encodeResourceToString(outParams);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
//@formatter:off
Parameters resp = client
.operation()
.onServer()
.named("$SOMEOPERATION")
.withNoParameters(Parameters.class).execute();
//@formatter:on
assertEquals("http://example.com/fhir/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString());
assertEquals(respString, p.encodeResourceToString(resp));
assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
assertEquals(extractBody(capt, idx), reqString);
assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod());
idx++;
//@formatter:off
resp = client
.operation()
.onType(Patient.class)
.named("$SOMEOPERATION")
.withNoParameters(Parameters.class).execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString());
assertEquals(respString, p.encodeResourceToString(resp));
assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
assertEquals(extractBody(capt, idx), reqString);
assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod());
idx++;
//@formatter:off
resp = client
.operation()
.onInstance(new IdDt("Patient", "123"))
.named("$SOMEOPERATION")
.withNoParameters(Parameters.class).execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient/123/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString());
assertEquals(respString, p.encodeResourceToString(resp));
assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
assertEquals(extractBody(capt, idx), reqString);
assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod());
idx++;
// @formatter:off
resp = client
.operation()
.onInstance(new IdDt("http://foo.com/bar/baz/Patient/123/_history/22"))
.named("$SOMEOPERATION")
.withNoParameters(Parameters.class)
.execute();
// @formatter:on
assertEquals("http://example.com/fhir/Patient/123/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString());
idx++;
}
@Test
public void testOperationAsGetWithNoInParameters() throws Exception {
IParser p = ourCtx.newXmlParser();
Parameters outParams = new Parameters();
outParams.addParameter().setValue(new StringDt("STRINGVALOUT1"));
outParams.addParameter().setValue(new StringDt("STRINGVALOUT2"));
final String respString = p.encodeResourceToString(outParams);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
//@formatter:off
Parameters resp = client
.operation()
.onServer()
.named("$SOMEOPERATION")
.withNoParameters(Parameters.class)
.useHttpGet()
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString());
assertEquals(respString, p.encodeResourceToString(resp));
assertEquals("GET", capt.getAllValues().get(idx).getRequestLine().getMethod());
idx++;
//@formatter:off
resp = client
.operation()
.onType(Patient.class)
.named("$SOMEOPERATION")
.withNoParameters(Parameters.class)
.useHttpGet()
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString());
assertEquals(respString, p.encodeResourceToString(resp));
assertEquals("GET", capt.getAllValues().get(idx).getRequestLine().getMethod());
idx++;
//@formatter:off
resp = client
.operation()
.onInstance(new IdDt("Patient", "123"))
.named("$SOMEOPERATION")
.withNoParameters(Parameters.class)
.useHttpGet()
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient/123/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString());
assertEquals(respString, p.encodeResourceToString(resp));
assertEquals("GET", capt.getAllValues().get(idx).getRequestLine().getMethod());
idx++;
// @formatter:off
resp = client
.operation()
.onInstance(new IdDt("http://foo.com/bar/baz/Patient/123/_history/22"))
.named("$SOMEOPERATION")
.withNoParameters(Parameters.class)
.useHttpGet()
.execute();
// @formatter:on
assertEquals("http://example.com/fhir/Patient/123/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString());
idx++;
}
@Test
public void testOperationAsGetWithInParameters() throws Exception {
IParser p = ourCtx.newXmlParser();
Parameters inParams = new Parameters();
inParams.addParameter().setName("param1").setValue(new StringDt("STRINGVALIN1"));
inParams.addParameter().setName("param1").setValue(new StringDt("STRINGVALIN1b"));
inParams.addParameter().setName("param2").setValue(new StringDt("STRINGVALIN2"));
Parameters outParams = new Parameters();
outParams.addParameter().setValue(new StringDt("STRINGVALOUT1"));
outParams.addParameter().setValue(new StringDt("STRINGVALOUT2"));
final String respString = p.encodeResourceToString(outParams);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
//@formatter:off
Parameters resp = client
.operation()
.onServer()
.named("$SOMEOPERATION")
.withParameters(inParams)
.useHttpGet()
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/$SOMEOPERATION?param1=STRINGVALIN1&param1=STRINGVALIN1b&param2=STRINGVALIN2", capt.getAllValues().get(idx).getURI().toASCIIString());
assertEquals(respString, p.encodeResourceToString(resp));
assertEquals("GET", capt.getAllValues().get(idx).getRequestLine().getMethod());
idx++;
//@formatter:off
resp = client
.operation()
.onType(Patient.class)
.named("$SOMEOPERATION")
.withParameters(inParams)
.useHttpGet()
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient/$SOMEOPERATION?param1=STRINGVALIN1&param1=STRINGVALIN1b&param2=STRINGVALIN2", capt.getAllValues().get(idx).getURI().toASCIIString());
assertEquals(respString, p.encodeResourceToString(resp));
assertEquals("GET", capt.getAllValues().get(idx).getRequestLine().getMethod());
idx++;
//@formatter:off
resp = client
.operation()
.onInstance(new IdDt("Patient", "123"))
.named("$SOMEOPERATION")
.withParameters(inParams)
.useHttpGet()
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient/123/$SOMEOPERATION?param1=STRINGVALIN1&param1=STRINGVALIN1b&param2=STRINGVALIN2", capt.getAllValues().get(idx).getURI().toASCIIString());
assertEquals(respString, p.encodeResourceToString(resp));
assertEquals("GET", capt.getAllValues().get(idx).getRequestLine().getMethod());
idx++;
// @formatter:off
resp = client
.operation()
.onInstance(new IdDt("http://foo.com/bar/baz/Patient/123/_history/22"))
.named("$SOMEOPERATION")
.withParameters(inParams)
.useHttpGet()
.execute();
// @formatter:on
assertEquals("http://example.com/fhir/Patient/123/$SOMEOPERATION?param1=STRINGVALIN1&param1=STRINGVALIN1b&param2=STRINGVALIN2", capt.getAllValues().get(idx).getURI().toASCIIString());
idx++;
}
@Test
public void testOperationWithBundleResponse() throws Exception {
IParser p = ourCtx.newXmlParser();

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -162,6 +163,19 @@ public class OperationServerTest {
assertEquals("RET1", resp.getParameter().get(0).getName());
}
@Test
public void testOperationWithBundleProviderResponse() throws Exception {
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/$OP_INSTANCE_BUNDLE_PROVIDER?_pretty=true");
HttpResponse status = ourClient.execute(httpPost);
assertEquals(200, status.getStatusLine().getStatusCode());
String response = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info(response);
Bundle resp = ourCtx.newXmlParser().parseResource(Bundle.class, response);
}
@Test
public void testOperationWithListParam() throws Exception {
Parameters p = new Parameters();
@ -317,6 +331,9 @@ public class OperationServerTest {
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer();
servlet.setPagingProvider(new FifoMemoryPagingProvider(10).setDefaultPageSize(2));
servlet.setFhirContext(ourCtx);
servlet.setResourceProviders(new PatientProvider());
servlet.setPlainProviders(new PlainProvider());
@ -334,6 +351,22 @@ public class OperationServerTest {
public static class PlainProvider {
//@formatter:off
@Operation(name="$OP_INSTANCE_BUNDLE_PROVIDER", idempotent=true)
public IBundleProvider opInstanceReturnsBundleProvider() {
ourLastMethod = "$OP_INSTANCE_BUNDLE_PROVIDER";
List<IResource> resources = new ArrayList<IResource>();
for (int i =0; i < 100;i++) {
Patient p = new Patient();
p.setId("Patient/" + i);
p.addName().addFamily("Patient " + i);
resources.add(p);
}
return new SimpleBundleProvider(resources);
}
//@formatter:off
@Operation(name="$OP_SERVER")
public Parameters opServer(

View File

@ -14,6 +14,7 @@ import java.util.Set;
import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServletRequest;
import org.hl7.fhir.instance.model.IBaseResource;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
@ -26,7 +27,9 @@ import ca.uhn.fhir.model.dstu2.resource.Conformance.RestResource;
import ca.uhn.fhir.model.dstu2.resource.DiagnosticReport;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.IncludeParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
@ -37,9 +40,9 @@ import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.server.provider.dstu2.ServerConformanceProvider;
public class ServerConformanceProviderTest {
public class ServerConformanceProviderDstu2Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerConformanceProviderTest.class);
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerConformanceProviderDstu2Test.class);
private FhirContext myCtx = new FhirContext();
@Test
@ -75,6 +78,58 @@ public class ServerConformanceProviderTest {
}
@Test
public void testEverythingOperationDocumentation() throws Exception {
RestfulServer rs = new RestfulServer();
rs.setProviders(new ProviderWithOperations());
ServerConformanceProvider sc = new ServerConformanceProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
Conformance conformance = sc.getServerConformance(createHttpServletRequest());
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
}
@Test
public void testOperationDocumentation() throws Exception {
RestfulServer rs = new RestfulServer();
rs.setProviders(new SearchProvider());
ServerConformanceProvider sc = new ServerConformanceProvider(rs);
rs.setServerConformanceProvider(sc);
rs.init(createServletConfig());
boolean found=false;
Collection<ResourceBinding> resourceBindings = rs.getResourceBindings();
for (ResourceBinding resourceBinding : resourceBindings) {
if (resourceBinding.getResourceName().equals("Patient")) {
List<BaseMethodBinding<?>> methodBindings = resourceBinding.getMethodBindings();
SearchMethodBinding binding = (SearchMethodBinding) methodBindings.get(0);
SearchParameter param = (SearchParameter) binding.getParameters().iterator().next();
assertEquals("The patient's identifier (MRN or other card number)", param.getDescription());
found=true;
}
}
assertTrue(found);
Conformance conformance = sc.getServerConformance(createHttpServletRequest());
String conf = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
assertThat(conf, containsString("<documentation value=\"The patient's identifier (MRN or other card number)\"/>"));
assertThat(conf, containsString("<type value=\"token\"/>"));
}
@Test
public void testValidateGeneratedStatement() throws Exception {
@ -203,9 +258,23 @@ public class ServerConformanceProviderTest {
return null;
}
}
public static class ProviderWithOperations implements IResourceProvider {
@Operation(name="everything", idempotent=true)
public ca.uhn.fhir.rest.server.IBundleProvider everything(
javax.servlet.http.HttpServletRequest theServletRequest,
@IdParam ca.uhn.fhir.model.primitive.IdDt theId) {
return null;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return Patient.class;
}
}
private HttpServletRequest createHttpServletRequest() {
HttpServletRequest req = mock(HttpServletRequest.class);

View File

@ -112,4 +112,28 @@ public class ${className}ResourceProvider extends JpaResourceProvider${versionCa
endRequest(theServletRequest);
}
}
#if ( $version != 'dstu' && (${className} == 'Patient' || ${className} == 'Encounter') )
@Operation(name="everything", idempotent=true)
public ca.uhn.fhir.rest.server.IBundleProvider everything(
javax.servlet.http.HttpServletRequest theServletRequest,
@IdParam ca.uhn.fhir.model.primitive.IdDt theId) {
startRequest(theServletRequest);
try {
SearchParameterMap paramMap = new SearchParameterMap();
paramMap.setRevIncludes(Collections.singleton(new Include("*")));
paramMap.setIncludes(Collections.singleton(new Include("*")));
paramMap.add("_id", new StringParam(theId.getIdPart()));
ca.uhn.fhir.rest.server.IBundleProvider retVal = getDao().search(paramMap);
return retVal;
} finally {
endRequest(theServletRequest);
}
}
#end
}

View File

@ -408,10 +408,35 @@
</p>
<macro name="snippet">
<param name="id" value="operation" />
<param name="file"
value="examples/src/main/java/example/GenericClientExample.java" />
<param name="file" value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
</section>
<p>
Note that if the operation does not require any input parameters,
you may also invoke the operation using the following form. Note that
the <code>withNoParameters</code> still requires you to provide the
type of the Parameters resource so that it can return the correct type in
the response.
</p>
<macro name="snippet">
<param name="id" value="operationNoIn" />
<param name="file" value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
<subsection name="Using the HTTP GET Form">
<p>
By default, the client will invoke operations using the HTTP POST form.
The FHIR specification also allows requests to use the HTTP GET verb
if the operation is idempotent and has no composite/resource parameters.
Use the following form to invoke operation with HTTP GET.
</p>
<macro name="snippet">
<param name="id" value="operationHttpGet" />
<param name="file" value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
</subsection>
</section>
</body>