Fixing test related to summary and elements mode
This commit is contained in:
parent
44ac164eca
commit
11376024fa
|
@ -39,6 +39,7 @@ import ca.uhn.fhir.rest.annotation.Count;
|
|||
import ca.uhn.fhir.rest.annotation.Create;
|
||||
import ca.uhn.fhir.rest.annotation.Delete;
|
||||
import ca.uhn.fhir.rest.annotation.DeleteTags;
|
||||
import ca.uhn.fhir.rest.annotation.Elements;
|
||||
import ca.uhn.fhir.rest.annotation.GetTags;
|
||||
import ca.uhn.fhir.rest.annotation.History;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
|
@ -59,6 +60,7 @@ import ca.uhn.fhir.rest.annotation.Validate;
|
|||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.api.SortOrderEnum;
|
||||
import ca.uhn.fhir.rest.api.SortSpec;
|
||||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
||||
import ca.uhn.fhir.rest.api.ValidationModeEnum;
|
||||
import ca.uhn.fhir.rest.client.api.IBasicClient;
|
||||
import ca.uhn.fhir.rest.client.api.IRestfulClient;
|
||||
|
@ -102,6 +104,17 @@ public List<Organization> getAllOrganizations() {
|
|||
}
|
||||
//END SNIPPET: searchAll
|
||||
|
||||
|
||||
//START SNIPPET: summaryAndElements
|
||||
@Search
|
||||
public List<Patient> search(
|
||||
SummaryEnum theSummary, // will receive the summary (no annotation required)
|
||||
@Elements Set<String> theElements // (requires the @Elements annotation)
|
||||
) {
|
||||
return null; // todo: populate
|
||||
}
|
||||
//END SNIPPET: summaryAndElements
|
||||
|
||||
//START SNIPPET: searchCompartment
|
||||
public class PatientRp implements IResourceProvider {
|
||||
|
||||
|
@ -705,7 +718,7 @@ public MethodOutcome createPatient(@ResourceParam Patient thePatient) {
|
|||
// You can also add an OperationOutcome resource to return
|
||||
// This part is optional though:
|
||||
OperationOutcome outcome = new OperationOutcome();
|
||||
outcome.addIssue().setDetails("One minor issue detected");
|
||||
outcome.addIssue().setDiagnostics("One minor issue detected");
|
||||
retVal.setOperationOutcome(outcome);
|
||||
|
||||
return retVal;
|
||||
|
@ -841,7 +854,7 @@ public MethodOutcome updatePatient(@IdParam IdDt theId, @ResourceParam Patient t
|
|||
// You can also add an OperationOutcome resource to return
|
||||
// This part is optional though:
|
||||
OperationOutcome outcome = new OperationOutcome();
|
||||
outcome.addIssue().setDetails("One minor issue detected");
|
||||
outcome.addIssue().setDiagnostics("One minor issue detected");
|
||||
retVal.setOperationOutcome(outcome);
|
||||
|
||||
// If your server supports creating resources during an update if they don't already exist
|
||||
|
@ -881,7 +894,7 @@ public MethodOutcome validatePatient(@ResourceParam Patient thePatient,
|
|||
// You may also add an OperationOutcome resource to return
|
||||
// This part is optional though:
|
||||
OperationOutcome outcome = new OperationOutcome();
|
||||
outcome.addIssue().setSeverity(IssueSeverityEnum.WARNING).setDetails("One minor issue detected");
|
||||
outcome.addIssue().setSeverity(IssueSeverityEnum.WARNING).setDiagnostics("One minor issue detected");
|
||||
retVal.setOperationOutcome(outcome);
|
||||
|
||||
return retVal;
|
||||
|
@ -1127,7 +1140,7 @@ public List<IResource> transaction(@TransactionParam List<IResource> theResource
|
|||
// If wanted, you may optionally also return an OperationOutcome resource
|
||||
// If present, the OperationOutcome must come first in the returned list.
|
||||
OperationOutcome oo = new OperationOutcome();
|
||||
oo.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDetails("Completed successfully");
|
||||
oo.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDiagnostics("Completed successfully");
|
||||
retVal.add(0, oo);
|
||||
|
||||
return retVal;
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.io.Reader;
|
|||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -68,6 +69,7 @@ import ca.uhn.fhir.model.api.Tag;
|
|||
import ca.uhn.fhir.model.api.TagList;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
|
||||
public abstract class BaseParser implements IParser {
|
||||
|
||||
|
@ -133,6 +135,10 @@ public abstract class BaseParser implements IParser {
|
|||
myNext = null;
|
||||
} else if (myNext.getDef().getElementName().equals("id")) {
|
||||
myNext = null;
|
||||
} else if (!myNext.shouldBeEncoded()) {
|
||||
myNext = null;
|
||||
} else if (isSummaryMode() && !myNext.getDef().isSummary()) {
|
||||
myNext = null;
|
||||
} else if (myNext.getDef() instanceof RuntimeChildNarrativeDefinition) {
|
||||
if (isSuppressNarratives() || isSummaryMode()) {
|
||||
myNext = null;
|
||||
|
@ -143,10 +149,6 @@ public abstract class BaseParser implements IParser {
|
|||
if (theContainedResource) {
|
||||
myNext = null;
|
||||
}
|
||||
} else if (isSummaryMode() && !myNext.getDef().isSummary()) {
|
||||
myNext = null;
|
||||
} else if (!myNext.shouldBeEncoded()) {
|
||||
myNext = null;
|
||||
}
|
||||
|
||||
} while (myNext == null);
|
||||
|
@ -533,7 +535,11 @@ public abstract class BaseParser implements IParser {
|
|||
IBaseMetaType metaValue;
|
||||
if (theValues != null && theValues.size() >= 1) {
|
||||
metaValue = (IBaseMetaType) theValues.iterator().next();
|
||||
metaValue = metaValue.copy();
|
||||
try {
|
||||
metaValue = (IBaseMetaType) metaValue.getClass().getMethod("copy").invoke(metaValue);
|
||||
} catch (Exception e) {
|
||||
throw new InternalErrorException("Failed to duplicate meta", e);
|
||||
}
|
||||
} else {
|
||||
metaValue = (IBaseMetaType) metaChildUncast1.newInstance();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
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;
|
||||
|
||||
/**
|
||||
* On a method which returns resource(s), a parameter of type
|
||||
* <code>Set<String></code> with this annotation will be passed the
|
||||
* contents of the <code>_elements</code> parameter
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.PARAMETER)
|
||||
public @interface Elements {
|
||||
// just a marker
|
||||
}
|
|
@ -246,6 +246,16 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
|||
// Determine response encoding
|
||||
EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequest.getServletRequest());
|
||||
|
||||
// _elements
|
||||
Set<String> elements = ElementsParameter.getElementsValueOrNull(theRequest);
|
||||
if (elements != null && summaryMode != null && !summaryMode.equals(Collections.singleton(SummaryEnum.FALSE))) {
|
||||
throw new InvalidRequestException("Cannot combine the " + Constants.PARAM_SUMMARY + " and " + Constants.PARAM_ELEMENTS + " parameters");
|
||||
}
|
||||
Set<String> elementsAppliesTo = null;
|
||||
if (elements != null && isNotBlank(myResourceName)) {
|
||||
elementsAppliesTo = Collections.singleton(myResourceName);
|
||||
}
|
||||
|
||||
// Is this request coming from a browser
|
||||
String uaHeader = theRequest.getServletRequest().getHeader("user-agent");
|
||||
boolean requestIsBrowser = false;
|
||||
|
@ -332,7 +342,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
|||
}
|
||||
|
||||
RestfulServerUtils.streamResponseAsResource(theServer, response, resource, responseEncoding, prettyPrint, requestIsBrowser, summaryMode, Constants.STATUS_HTTP_200_OK, respondGzip,
|
||||
theRequest.getFhirServerBase(), isAddContentLocationHeader());
|
||||
theRequest.getFhirServerBase(), isAddContentLocationHeader(), elements, elementsAppliesTo);
|
||||
break;
|
||||
} else {
|
||||
Set<Include> includes = getRequestIncludesFromParams(params);
|
||||
|
@ -367,7 +377,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
|||
}
|
||||
}
|
||||
RestfulServerUtils.streamResponseAsResource(theServer, response, resBundle, responseEncoding, prettyPrint, requestIsBrowser, summaryMode,
|
||||
Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), theRequest.getFhirServerBase(), isAddContentLocationHeader());
|
||||
Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), theRequest.getFhirServerBase(), isAddContentLocationHeader(), elements, elementsAppliesTo);
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -392,19 +402,12 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Obje
|
|||
}
|
||||
|
||||
RestfulServerUtils.streamResponseAsResource(theServer, response, resource, responseEncoding, prettyPrint, requestIsBrowser, summaryMode, Constants.STATUS_HTTP_200_OK, respondGzip,
|
||||
theRequest.getFhirServerBase(), isAddContentLocationHeader());
|
||||
theRequest.getFhirServerBase(), isAddContentLocationHeader(), elements, elementsAppliesTo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOmitEntries(Set<SummaryEnum> theSummaryMode) {
|
||||
if (theSummaryMode == null || !theSummaryMode.contains(SummaryEnum.COUNT)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the response include a Content-Location header. Search method bunding (and any others?) may override this to disable the content-location, since it doesn't make sense
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
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 static org.apache.commons.lang3.StringUtils.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
||||
import ca.uhn.fhir.rest.param.CollectionBinder;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
public class ElementsParameter implements IParameter {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private Class<? extends Collection> myInnerCollectionType;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments, IBaseResource theTargetResource) throws InternalErrorException {
|
||||
if (theSourceClientArgument instanceof Collection) {
|
||||
StringBuilder values = new StringBuilder();
|
||||
for (String next : (Collection<String>) theSourceClientArgument) {
|
||||
if (isNotBlank(next)) {
|
||||
if (values.length() > 0) {
|
||||
values.append(',');
|
||||
}
|
||||
values.append(next);
|
||||
}
|
||||
}
|
||||
theTargetQueryArguments.put(Constants.PARAM_ELEMENTS, Collections.singletonList(values.toString()));
|
||||
} else {
|
||||
String elements = (String) theSourceClientArgument;
|
||||
if (elements != null) {
|
||||
theTargetQueryArguments.put(Constants.PARAM_ELEMENTS, Collections.singletonList(elements));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
|
||||
Set<String> value = getElementsValueOrNull(theRequest);
|
||||
if (value == null || value.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (myInnerCollectionType == null) {
|
||||
return StringUtils.join(value, ',');
|
||||
}
|
||||
|
||||
try {
|
||||
Collection retVal = myInnerCollectionType.newInstance();
|
||||
retVal.addAll(value);
|
||||
return retVal;
|
||||
} catch (InstantiationException e) {
|
||||
throw new InternalErrorException("Failed to instantiate " + myInnerCollectionType, e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new InternalErrorException("Failed to instantiate " + myInnerCollectionType, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<String> getElementsValueOrNull(RequestDetails theRequest) {
|
||||
String[] summary = theRequest.getParameters().get(Constants.PARAM_ELEMENTS);
|
||||
|
||||
if (summary != null && summary.length > 0) {
|
||||
Set<String> retVal = new HashSet<String>();
|
||||
for (String next : summary) {
|
||||
StringTokenizer tok = new StringTokenizer(next, ",");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String token = tok.nextToken();
|
||||
if (isNotBlank(token)) {
|
||||
retVal.add(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (retVal.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return retVal;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
|
||||
if (theOuterCollectionType != null) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + theMethod.getDeclaringClass().getCanonicalName() + "' is of type " + SummaryEnum.class + " but can not be a collection of collections");
|
||||
}
|
||||
if (theInnerCollectionType != null) {
|
||||
myInnerCollectionType = CollectionBinder.getInstantiableCollectionType(theInnerCollectionType, SummaryEnum.class.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -51,6 +51,7 @@ import ca.uhn.fhir.model.primitive.StringDt;
|
|||
import ca.uhn.fhir.parser.IParser;
|
||||
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
|
||||
import ca.uhn.fhir.rest.annotation.Count;
|
||||
import ca.uhn.fhir.rest.annotation.Elements;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.IncludeParam;
|
||||
import ca.uhn.fhir.rest.annotation.Operation;
|
||||
|
@ -444,6 +445,8 @@ public class MethodUtil {
|
|||
param = new NullParameter();
|
||||
} else if (nextAnnotation instanceof ServerBase) {
|
||||
param = new ServerBaseParamBinder();
|
||||
} else if (nextAnnotation instanceof Elements) {
|
||||
param = new ElementsParameter();
|
||||
} else if (nextAnnotation instanceof Since) {
|
||||
param = new SinceParameter();
|
||||
} else if (nextAnnotation instanceof Count) {
|
||||
|
|
|
@ -41,6 +41,7 @@ import ca.uhn.fhir.model.api.Bundle;
|
|||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||
import ca.uhn.fhir.rest.annotation.Elements;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.Read;
|
||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||
|
@ -105,6 +106,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem
|
|||
public List<Class<?>> getAllowableParamAnnotations() {
|
||||
ArrayList<Class<?>> retVal = new ArrayList<Class<?>>();
|
||||
retVal.add(IdParam.class);
|
||||
retVal.add(Elements.class);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
|||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
||||
import ca.uhn.fhir.rest.method.BaseMethodBinding;
|
||||
import ca.uhn.fhir.rest.method.ConformanceMethodBinding;
|
||||
import ca.uhn.fhir.rest.method.ElementsParameter;
|
||||
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||
|
@ -451,6 +452,8 @@ public class RestfulServer extends HttpServlet {
|
|||
boolean requestIsBrowser = requestIsBrowser(theRequest.getServletRequest());
|
||||
Set<SummaryEnum> summaryMode = RestfulServerUtils.determineSummaryMode(theRequest);
|
||||
boolean respondGzip = theRequest.isRespondGzip();
|
||||
Set<String> elements = ElementsParameter.getElementsValueOrNull(theRequest);
|
||||
Set<String> elementsAppliesTo = null; // TODO: persist this across pages
|
||||
|
||||
IVersionSpecificBundleFactory bundleFactory = getFhirContext().newBundleFactory();
|
||||
|
||||
|
@ -489,7 +492,7 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
}
|
||||
RestfulServerUtils.streamResponseAsResource(this, theResponse, resBundle, responseEncoding, prettyPrint, requestIsBrowser, summaryMode, Constants.STATUS_HTTP_200_OK,
|
||||
theRequest.isRespondGzip(), theRequest.getFhirServerBase(), false);
|
||||
theRequest.isRespondGzip(), theRequest.getFhirServerBase(), false, elements, elementsAppliesTo);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -410,7 +410,7 @@ public class RestfulServerUtils {
|
|||
}
|
||||
|
||||
public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IBaseResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, Set<SummaryEnum> theNarrativeMode, int stausCode, boolean theRespondGzip,
|
||||
String theServerBase, boolean theAddContentLocationHeader) throws IOException {
|
||||
String theServerBase, boolean theAddContentLocationHeader, Set<String> theElements, Set<String> theElementsAppliesTo) throws IOException {
|
||||
theHttpResponse.setStatus(stausCode);
|
||||
|
||||
if (theAddContentLocationHeader && theResource.getIdElement() != null && theResource.getIdElement().hasIdPart() && isNotBlank(theServerBase)) {
|
||||
|
@ -506,6 +506,14 @@ public class RestfulServerUtils {
|
|||
} else {
|
||||
IParser parser = getNewParser(theServer.getFhirContext(), responseEncoding, thePrettyPrint, theNarrativeMode);
|
||||
parser.setServerBaseUrl(theServerBase);
|
||||
if (theElements != null && theElements.size() > 0) {
|
||||
Set<String> elements = new HashSet<String>();
|
||||
for (String next : theElements) {
|
||||
elements.add("*." + next);
|
||||
}
|
||||
parser.setEncodeElements(elements);
|
||||
parser.setEncodeElementsAppliesToResourceTypes(theElementsAppliesTo);
|
||||
}
|
||||
parser.encodeResourceToWriter(theResource, writer);
|
||||
}
|
||||
} finally {
|
||||
|
|
|
@ -75,7 +75,7 @@ public class ExceptionHandlingInterceptor extends InterceptorAdapter {
|
|||
|
||||
boolean requestIsBrowser = RestfulServer.requestIsBrowser(theRequest);
|
||||
String fhirServerBase = theRequestDetails.getFhirServerBase();
|
||||
RestfulServerUtils.streamResponseAsResource(theRequestDetails.getServer(), theResponse, oo, RestfulServerUtils.determineResponseEncodingNoDefault(theRequest), true, requestIsBrowser, Collections.singleton(SummaryEnum.FALSE), statusCode, false, fhirServerBase, false);
|
||||
RestfulServerUtils.streamResponseAsResource(theRequestDetails.getServer(), theResponse, oo, RestfulServerUtils.determineResponseEncodingNoDefault(theRequest), true, requestIsBrowser, Collections.singleton(SummaryEnum.FALSE), statusCode, false, fhirServerBase, false, null, null);
|
||||
|
||||
// theResponse.setStatus(statusCode);
|
||||
// theRequestDetails.getServer().addHeadersToResponse(theResponse);
|
||||
|
|
|
@ -32,6 +32,4 @@ public interface IBaseMetaType extends ICompositeType {
|
|||
|
||||
String getVersionId();
|
||||
|
||||
IBaseMetaType copy();
|
||||
|
||||
}
|
||||
|
|
|
@ -1220,14 +1220,22 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
/**
|
||||
* This method is invoked immediately before storing a new resource, or an
|
||||
* update to an existing resource to allow the DAO to ensure that it is valid
|
||||
* for persistence. By default, no validation is performed, but subclasses
|
||||
* may override to provide specific behaviour.
|
||||
* for persistence. By default, checks for the "subsetted" tag and rejects
|
||||
* resources which have it. Subclasses should call the superclass implementation to
|
||||
* preserve this check.
|
||||
*
|
||||
* @param theResource
|
||||
* The resource that is about to be persisted
|
||||
*/
|
||||
protected void validateResourceForStorage(T theResource) {
|
||||
// nothing
|
||||
IResource res = (IResource)theResource;
|
||||
TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(res);
|
||||
if (tagList != null) {
|
||||
Tag tag = tagList.getTag(Constants.TAG_SUBSETTED_SYSTEM, Constants.TAG_SUBSETTED_CODE);
|
||||
if (tag != null) {
|
||||
throw new UnprocessableEntityException("Resource contains the 'subsetted' tag, and must not be stored as it may contain a subset of available data");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static String normalizeString(String theString) {
|
||||
|
|
|
@ -83,6 +83,7 @@ import ca.uhn.fhir.rest.param.StringParam;
|
|||
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||
|
@ -309,6 +310,25 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateSummaryFails() {
|
||||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue("testCreateTextIdFails");
|
||||
p.addName().addFamily("Hello");
|
||||
|
||||
TagList tl = new TagList();
|
||||
tl.addTag(Constants.TAG_SUBSETTED_SYSTEM, Constants.TAG_SUBSETTED_CODE);
|
||||
ResourceMetadataKeyEnum.TAG_LIST.put(p, tl);
|
||||
|
||||
try {
|
||||
ourPatientDao.create(p);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), containsString("subsetted"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateWithIfNoneExistBasic() {
|
||||
String methodName = "testCreateWithIfNoneExistBasic";
|
||||
|
|
|
@ -11,7 +11,9 @@ import java.nio.charset.Charset;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.io.input.ReaderInputStream;
|
||||
|
@ -51,6 +53,7 @@ import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
|
|||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.model.primitive.IntegerDt;
|
||||
import ca.uhn.fhir.rest.annotation.Elements;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.IncludeParam;
|
||||
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||
|
@ -827,6 +830,51 @@ public class ClientDstu1Test {
|
|||
idx++;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchWithElements() throws Exception {
|
||||
|
||||
final String msg = getPatientFeedWithOneResult();
|
||||
|
||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||
|
||||
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
|
||||
when(httpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
|
||||
@Override
|
||||
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
// httpResponse = new BasicHttpResponse(statusline, catalog, locale)
|
||||
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
|
||||
|
||||
ITestClientWithElements client = ourCtx.newRestfulClient(ITestClientWithElements.class, "http://foo");
|
||||
|
||||
int idx = 0;
|
||||
|
||||
client.getPatientWithIncludes((String) null);
|
||||
assertEquals("http://foo/Patient", capt.getAllValues().get(idx).getURI().toString());
|
||||
idx++;
|
||||
|
||||
client.getPatientWithIncludes((Set<String>) null);
|
||||
assertEquals("http://foo/Patient", capt.getAllValues().get(idx).getURI().toString());
|
||||
idx++;
|
||||
|
||||
client.getPatientWithIncludes("test");
|
||||
assertEquals("http://foo/Patient?_elements=test", capt.getAllValues().get(idx).getURI().toString());
|
||||
idx++;
|
||||
|
||||
client.getPatientWithIncludes("test,foo");
|
||||
assertEquals("http://foo/Patient?_elements=test%2Cfoo", capt.getAllValues().get(idx).getURI().toString());
|
||||
idx++;
|
||||
|
||||
client.getPatientWithIncludes(new HashSet<String>(Arrays.asList("test","foo", "")));
|
||||
assertEquals("http://foo/Patient?_elements=test%2Cfoo", capt.getAllValues().get(idx).getURI().toString());
|
||||
idx++;
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchByCompartment() throws Exception {
|
||||
|
||||
|
@ -1316,4 +1364,13 @@ public class ClientDstu1Test {
|
|||
|
||||
}
|
||||
|
||||
public interface ITestClientWithElements extends IBasicClient {
|
||||
@Search()
|
||||
public List<Patient> getPatientWithIncludes(@Elements String theElements);
|
||||
|
||||
@Search()
|
||||
public List<Patient> getPatientWithIncludes(@Elements Set<String> theElements);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
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.Patient;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.MaritalStatusCodesEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.annotation.Elements;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.Read;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
|
||||
public class ElementsParamTest {
|
||||
|
||||
private static CloseableHttpClient ourClient;
|
||||
private static FhirContext ourCtx = FhirContext.forDstu2();
|
||||
private static Set<String> ourLastElements;
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ElementsParamTest.class);
|
||||
private static int ourPort;
|
||||
private static Server ourServer;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
ourLastElements = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadSummaryData() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_elements=name,maritalStatus");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
ourLog.info(responseContent);
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertEquals(Constants.CT_FHIR_XML + Constants.CHARSET_UTF8_CTSUFFIX, status.getEntity().getContentType().getValue());
|
||||
assertThat(responseContent, not(containsString("<Bundle")));
|
||||
assertThat(responseContent, (containsString("<Patien")));
|
||||
assertThat(responseContent, not(containsString("<div>THE DIV</div>")));
|
||||
assertThat(responseContent, (containsString("family")));
|
||||
assertThat(responseContent, (containsString("maritalStatus")));
|
||||
assertThat(ourLastElements, containsInAnyOrder("name", "maritalStatus"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadSummaryTrue() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1?_elements=name");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
ourLog.info(responseContent);
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertEquals(Constants.CT_FHIR_XML + Constants.CHARSET_UTF8_CTSUFFIX, status.getEntity().getContentType().getValue());
|
||||
assertThat(responseContent, not(containsString("<Bundle")));
|
||||
assertThat(responseContent, (containsString("<Patien")));
|
||||
assertThat(responseContent, not(containsString("<div>THE DIV</div>")));
|
||||
assertThat(responseContent, (containsString("family")));
|
||||
assertThat(responseContent, not(containsString("maritalStatus")));
|
||||
assertThat(ourLastElements, containsInAnyOrder("name"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchSummaryData() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_elements=name,maritalStatus");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
ourLog.info(responseContent);
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertThat(responseContent, containsString("<Patient"));
|
||||
assertThat(responseContent, not(containsString("THE DIV")));
|
||||
assertThat(responseContent, containsString("family"));
|
||||
assertThat(responseContent, containsString("maritalStatus"));
|
||||
assertThat(ourLastElements, containsInAnyOrder("name", "maritalStatus"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchSummaryText() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_elements=text");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
ourLog.info(responseContent);
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
assertThat(responseContent, (containsString("<total value=\"1\"/>")));
|
||||
assertThat(responseContent, (containsString("entry")));
|
||||
assertThat(responseContent, (containsString("THE DIV")));
|
||||
assertThat(responseContent, not(containsString("family")));
|
||||
assertThat(responseContent, not(containsString("maritalStatus")));
|
||||
assertThat(ourLastElements, containsInAnyOrder("text"));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() throws Exception {
|
||||
ourServer.stop();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
ourPort = PortUtil.findFreePort();
|
||||
ourServer = new Server(ourPort);
|
||||
|
||||
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
RestfulServer servlet = new RestfulServer(ourCtx);
|
||||
|
||||
servlet.setResourceProviders(patientProvider);
|
||||
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 DummyPatientResourceProvider implements IResourceProvider {
|
||||
|
||||
@Override
|
||||
public Class<? extends IResource> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
|
||||
@Read
|
||||
public Patient read(@IdParam IdDt theId, @Elements Set<String> theElements) {
|
||||
ourLastElements = theElements;
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/1/_history/1");
|
||||
patient.getText().setDiv("<div>THE DIV</div>");
|
||||
patient.addName().addFamily("FAMILY");
|
||||
patient.setMaritalStatus(MaritalStatusCodesEnum.D);
|
||||
return patient;
|
||||
}
|
||||
|
||||
@Search()
|
||||
public Patient search(@Elements Set<String> theElements) {
|
||||
ourLastElements = theElements;
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/1/_history/1");
|
||||
patient.getText().setDiv("<div>THE DIV</div>");
|
||||
patient.addName().addFamily("FAMILY");
|
||||
patient.setMaritalStatus(MaritalStatusCodesEnum.D);
|
||||
return patient;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -96,6 +96,10 @@
|
|||
<action type="add">
|
||||
JPA server now implements the $validate-code operation
|
||||
</action>
|
||||
<action type="add" fix="125">
|
||||
HAPI-FHIR now has support for _summary and _elements parameters, in server, client,
|
||||
and JPA server.
|
||||
</action>
|
||||
</release>
|
||||
<release version="1.1" date="2015-07-13">
|
||||
<action type="add">
|
||||
|
|
|
@ -1525,6 +1525,25 @@
|
|||
|
||||
</subsection>
|
||||
|
||||
</section>
|
||||
|
||||
<!-- ****************************************************************** -->
|
||||
<!-- ****************************************************************** -->
|
||||
<!-- ****************************************************************** -->
|
||||
|
||||
<section name="_summary and _elements">
|
||||
|
||||
The <code>_summary</code> and <code>_elements</code> parameters are
|
||||
automatically handled by the server, so no coding is required to make this
|
||||
work. If you wish to add parameters to manually handle these fields however,
|
||||
the following example shows how to access these.
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="summaryAndElements" />
|
||||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
<a name="compartments" />
|
||||
</section>
|
||||
|
||||
|
|
|
@ -361,20 +361,66 @@
|
|||
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
</macro>
|
||||
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Accessing the _narrative parameter value">
|
||||
<subsection name="Subsetting: _summary and _elements parameters">
|
||||
|
||||
<p>
|
||||
There are different ways of
|
||||
<a href="./doc_narrative.html">generating narratives</a> for use on your server. HAPI's Server
|
||||
also provides a non-standard parameter called <code>_narrative</code> which can be used to
|
||||
control narrative behavour. If you add a parameter to any server (or annotation client) method
|
||||
with a type of <code>NarrativeModeEnum</code>, the value will be populated with the value
|
||||
of this URL parameter.
|
||||
FHIR allows for the a number of special behaviours where only certain
|
||||
portions of resources are returned, instead of the entire resource body.
|
||||
These behaviours are automatically supported in HAPI (as of HAPI 1.2)
|
||||
and no additional effort needs to be taken.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The following behaviours are automatically supported by the HAPI server:
|
||||
</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Parameter</td>
|
||||
<td>Description</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>_summary=true</td>
|
||||
<td>
|
||||
Resources will be returned with any elements not marked as summary elements
|
||||
omitted.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>_summary=text</td>
|
||||
<td>
|
||||
Only the narrative portion of returned resources will be returned. For a read/vread
|
||||
operation, the narrative will be served with a content type of <code>text/html</code>.
|
||||
for other operations, a Bundle will be returned but resources will only include
|
||||
the text element.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>_summary=data</td>
|
||||
<td>
|
||||
The narrative (text) portion of the resource will be omitted.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>_summary=count</td>
|
||||
<td>
|
||||
For a search, only Bundle.count will be returned.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>_elements=[element names]</td>
|
||||
<td>
|
||||
Only the given top level elements of returned resources will be returned, e.g for
|
||||
a Patient search: <code>_elements=name,contact</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</subsection>
|
||||
|
||||
</section>
|
||||
|
|
Loading…
Reference in New Issue