Fixing test related to summary and elements mode

This commit is contained in:
jamesagnew 2015-08-23 21:51:46 -04:00
parent 44ac164eca
commit 11376024fa
19 changed files with 551 additions and 1609 deletions

View File

@ -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;

View File

@ -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();
}

View File

@ -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&lt;String&gt;</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
}

View File

@ -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
*/

View File

@ -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());
}
}
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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 {

View File

@ -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);

View File

@ -32,6 +32,4 @@ public interface IBaseMetaType extends ICompositeType {
String getVersionId();
IBaseMetaType copy();
}

View File

@ -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) {

View File

@ -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";

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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">

View File

@ -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>

View File

@ -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>