Improvements to elements parameter as well as adding attributes to

RequestDetails
This commit is contained in:
James Agnew 2019-02-07 16:45:59 -05:00
parent 0f8c8d18e2
commit 810f1ad969
10 changed files with 545 additions and 302 deletions

View File

@ -63,7 +63,6 @@ public abstract class BaseParser implements IParser {
private boolean mySummaryMode;
private boolean mySuppressNarratives;
private Set<String> myDontStripVersionsFromReferencesAtPaths;
/**
* Constructor
*/
@ -72,6 +71,55 @@ public abstract class BaseParser implements IParser {
myErrorHandler = theParserErrorHandler;
}
List<ElementsPath> getDontEncodeElements() {
return myDontEncodeElements;
}
@Override
public void setDontEncodeElements(Set<String> theDontEncodeElements) {
if (theDontEncodeElements == null || theDontEncodeElements.isEmpty()) {
myDontEncodeElements = null;
} else {
myDontEncodeElements = theDontEncodeElements
.stream()
.map(ElementsPath::new)
.collect(Collectors.toList());
}
}
List<ElementsPath> getEncodeElements() {
return myEncodeElements;
}
@Override
public void setEncodeElements(Set<String> theEncodeElements) {
if (theEncodeElements == null || theEncodeElements.isEmpty()) {
myEncodeElements = null;
myEncodeElementsAppliesToResourceTypes = null;
} else {
myEncodeElements = theEncodeElements
.stream()
.map(ElementsPath::new)
.collect(Collectors.toList());
myEncodeElementsAppliesToResourceTypes = new HashSet<>();
for (String next : myEncodeElements.stream().map(t -> t.getPath().get(0).getName()).collect(Collectors.toList())) {
if (next.startsWith("*")) {
myEncodeElementsAppliesToResourceTypes = null;
break;
}
int dotIdx = next.indexOf('.');
if (dotIdx == -1) {
myEncodeElementsAppliesToResourceTypes.add(next);
} else {
myEncodeElementsAppliesToResourceTypes.add(next.substring(0, dotIdx));
}
}
}
}
protected Iterable<CompositeChildElement> compositeChildIterator(IBase theCompositeElement, final boolean theContainedResource, final CompositeChildElement theParent, EncodeContext theEncodeContext) {
BaseRuntimeElementCompositeDefinition<?> elementDef = (BaseRuntimeElementCompositeDefinition<?>) myContext.getElementDefinition(theCompositeElement.getClass());
@ -402,35 +450,6 @@ public abstract class BaseParser implements IParser {
return myDontStripVersionsFromReferencesAtPaths;
}
@Override
public void setEncodeElements(Set<String> theEncodeElements) {
if (theEncodeElements == null || theEncodeElements.isEmpty()) {
myEncodeElements = null;
myEncodeElementsAppliesToResourceTypes = null;
} else {
myEncodeElements = theEncodeElements
.stream()
.map(ElementsPath::new)
.collect(Collectors.toList());
myEncodeElementsAppliesToResourceTypes = new HashSet<>();
for (String next : myEncodeElements.stream().map(t -> t.getPath().get(0).getName()).collect(Collectors.toList())) {
if (next.startsWith("*")) {
myEncodeElementsAppliesToResourceTypes = null;
break;
}
int dotIdx = next.indexOf('.');
if (dotIdx == -1) {
myEncodeElementsAppliesToResourceTypes.add(next);
} else {
myEncodeElementsAppliesToResourceTypes.add(next.substring(0, dotIdx));
}
}
}
}
@Override
public IIdType getEncodeForceResourceId() {
return myEncodeForceResourceId;
@ -803,18 +822,6 @@ public abstract class BaseParser implements IParser {
}
}
@Override
public void setDontEncodeElements(Set<String> theDontEncodeElements) {
if (theDontEncodeElements == null || theDontEncodeElements.isEmpty()) {
myDontEncodeElements = null;
} else {
myDontEncodeElements = theDontEncodeElements
.stream()
.map(ElementsPath::new)
.collect(Collectors.toList());
}
}
@Override
public IParser setDontStripVersionsFromReferencesAtPaths(String... thePaths) {
if (thePaths == null) {
@ -963,6 +970,17 @@ public abstract class BaseParser implements IParser {
throw new DataFormatException(nextChild + " has no child of type " + theType);
}
protected boolean shouldEncodeResource(String theName) {
if (myDontEncodeElements != null) {
for (ElementsPath next : myDontEncodeElements) {
if (next.equalsPath(theName)) {
return false;
}
}
}
return true;
}
class ChildNameAndDef {
private final BaseRuntimeElementDefinition<?> myChildDef;
@ -1289,9 +1307,23 @@ public abstract class BaseParser implements IParser {
if (myResource != theOther.isResource()) {
return false;
}
if (myName.equals(theOther.getName())) {
String otherName = theOther.getName();
if (myName.equals(otherName)) {
return true;
}
/*
* This is here to handle situations where a path like
* Observation.valueQuantity has been specified as an include/exclude path,
* since we only know that path as
* Observation.value
* until we get to actually looking at the values there.
*/
if (myName.length() > otherName.length() && myName.startsWith(otherName)) {
char ch = myName.charAt(otherName.length());
if (Character.isUpperCase(ch)) {
return true;
}
}
if (myName.equals("*")) {
return true;
}

View File

@ -410,8 +410,17 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
continue;
}
String childName = childNameAndDef.getChildName();
theEncodeContext.pushPath(childName, false);
/*
* Often the two values below will be the same thing. There are cases though
* where they will not be. An example would be Observation.value, which is
* a choice type. If the value contains a Quantity, then:
* nextChildGenericName = "value"
* nextChildSpecificName = "valueQuantity"
*/
String nextChildSpecificName = childNameAndDef.getChildName();
String nextChildGenericName = nextChild.getElementName();
theEncodeContext.pushPath(nextChildGenericName, false);
BaseRuntimeElementDefinition<?> childDef = childNameAndDef.getChildDef();
boolean primitive = childDef.getChildType() == ChildTypeEnum.PRIMITIVE_DATATYPE;
@ -451,20 +460,20 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
}
if (currentChildName == null || !currentChildName.equals(childName)) {
if (currentChildName == null || !currentChildName.equals(nextChildSpecificName)) {
if (inArray) {
theEventWriter.endArray();
}
if (nextChild.getMax() > 1 || nextChild.getMax() == Child.MAX_UNLIMITED) {
beginArray(theEventWriter, childName);
beginArray(theEventWriter, nextChildSpecificName);
inArray = true;
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, nextChildElem, force, theEncodeContext);
} else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) {
// suppress narratives from contained resources
} else {
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName, theContainedResource, nextChildElem, false, theEncodeContext);
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, nextChildSpecificName, theContainedResource, nextChildElem, false, theEncodeContext);
}
currentChildName = childName;
currentChildName = nextChildSpecificName;
} else {
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, nextChildElem, force, theEncodeContext);
}
@ -594,6 +603,11 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, String theObjectNameOrNull,
boolean theContainedResource, IIdType theResourceId, EncodeContext theEncodeContext) throws IOException {
if (!super.shouldEncodeResource(theResDef.getName())) {
return;
}
if (!theContainedResource) {
super.containResourcesForEncoding(theResource);
}

View File

@ -215,9 +215,19 @@ public class XmlParser extends BaseParser /* implements IParser */ {
}
}
private void encodeChildElementToStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, IBase theElement, String childName, BaseRuntimeElementDefinition<?> childDef,
private void encodeChildElementToStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, BaseRuntimeChildDefinition theChildDefinition, IBase theElement, String theChildName, BaseRuntimeElementDefinition<?> childDef,
String theExtensionUrl, boolean theIncludedResource, CompositeChildElement theParent, EncodeContext theEncodeContext) throws XMLStreamException, DataFormatException {
theEncodeContext.pushPath(childName, false);
/*
* Often the two values below will be the same thing. There are cases though
* where they will not be. An example would be Observation.value, which is
* a choice type. If the value contains a Quantity, then:
* childGenericName = "value"
* theChildName = "valueQuantity"
*/
String childGenericName = theChildDefinition.getElementName();
theEncodeContext.pushPath(childGenericName, false);
try {
if (theElement == null || theElement.isEmpty()) {
@ -233,9 +243,9 @@ public class XmlParser extends BaseParser /* implements IParser */ {
switch (childDef.getChildType()) {
case ID_DATATYPE: {
IIdType value = IIdType.class.cast(theElement);
String encodedValue = "id".equals(childName) ? value.getIdPart() : value.getValue();
String encodedValue = "id".equals(theChildName) ? value.getIdPart() : value.getValue();
if (StringUtils.isNotBlank(encodedValue) || !super.hasNoExtensions(value)) {
theEventWriter.writeStartElement(childName);
theEventWriter.writeStartElement(theChildName);
if (StringUtils.isNotBlank(encodedValue)) {
theEventWriter.writeAttribute("value", encodedValue);
}
@ -248,7 +258,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
IPrimitiveType<?> pd = IPrimitiveType.class.cast(theElement);
String value = pd.getValueAsString();
if (value != null || !super.hasNoExtensions(pd)) {
theEventWriter.writeStartElement(childName);
theEventWriter.writeStartElement(theChildName);
String elementId = getCompositeElementId(theElement);
if (isNotBlank(elementId)) {
theEventWriter.writeAttribute("id", elementId);
@ -263,7 +273,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
}
case RESOURCE_BLOCK:
case COMPOSITE_DATATYPE: {
theEventWriter.writeStartElement(childName);
theEventWriter.writeStartElement(theChildName);
String elementId = getCompositeElementId(theElement);
if (isNotBlank(elementId)) {
theEventWriter.writeAttribute("id", elementId);
@ -291,9 +301,12 @@ public class XmlParser extends BaseParser /* implements IParser */ {
break;
}
case RESOURCE: {
theEventWriter.writeStartElement(childName);
IBaseResource resource = (IBaseResource) theElement;
String resourceName = myContext.getResourceDefinition(resource).getName();
if (!super.shouldEncodeResource(resourceName)) {
break;
}
theEventWriter.writeStartElement(theChildName);
theEncodeContext.pushPath(resourceName, true);
encodeResourceToXmlStreamWriter(resource, theEventWriter, false, theEncodeContext);
theEncodeContext.popPath();
@ -364,13 +377,13 @@ public class XmlParser extends BaseParser /* implements IParser */ {
RuntimeChildNarrativeDefinition child = (RuntimeChildNarrativeDefinition) nextChild;
String childName = nextChild.getChildNameByDatatype(child.getDatatype());
BaseRuntimeElementDefinition<?> type = child.getChildByName(childName);
encodeChildElementToStreamWriter(theResource, theEventWriter, narr, childName, type, null, theContainedResource, nextChildElem, theEncodeContext);
encodeChildElementToStreamWriter(theResource, theEventWriter, nextChild, narr, childName, type, null, theContainedResource, nextChildElem, theEncodeContext);
continue;
}
}
if (nextChild instanceof RuntimeChildContainedResources) {
encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, nextChildElem, theEncodeContext);
encodeChildElementToStreamWriter(theResource, theEventWriter, nextChild, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, nextChildElem, theEncodeContext);
} else {
List<? extends IBase> values = nextChild.getAccessor().getValues(theElement);
@ -402,11 +415,11 @@ public class XmlParser extends BaseParser /* implements IParser */ {
continue;
}
}
encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, getExtensionUrl(extension.getUrl()), theContainedResource, nextChildElem, theEncodeContext);
encodeChildElementToStreamWriter(theResource, theEventWriter, nextChild, nextValue, childName, childDef, getExtensionUrl(extension.getUrl()), theContainedResource, nextChildElem, theEncodeContext);
} else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) {
// suppress narratives from contained resources
} else {
encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, extensionUrl, theContainedResource, nextChildElem, theEncodeContext);
encodeChildElementToStreamWriter(theResource, theEventWriter, nextChild, nextValue, childName, childDef, extensionUrl, theContainedResource, nextChildElem, theEncodeContext);
}
}
@ -429,7 +442,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
}
theEventWriter.writeAttribute("url", extensionUrl);
encodeChildElementToStreamWriter(theResource, theEventWriter, nextValue, childName, childDef, null, theContainedResource, nextChildElem, theEncodeContext);
encodeChildElementToStreamWriter(theResource, theEventWriter, nextChild, nextValue, childName, childDef, null, theContainedResource, nextChildElem, theEncodeContext);
theEventWriter.writeEndElement();
}
@ -471,15 +484,15 @@ public class XmlParser extends BaseParser /* implements IParser */ {
}
private void encodeResourceToXmlStreamWriter(IBaseResource theResource, XMLStreamWriter theEventWriter, boolean theContainedResource, IIdType theResourceId, EncodeContext theEncodeContext) throws XMLStreamException {
if (!theContainedResource) {
super.containResourcesForEncoding(theResource);
}
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theResource);
if (resDef == null) {
throw new ConfigurationException("Unknown resource type: " + theResource.getClass());
}
if (!theContainedResource) {
super.containResourcesForEncoding(theResource);
}
theEventWriter.writeStartElement(resDef.getName());
theEventWriter.writeDefaultNamespace(FHIR_NS);
@ -609,7 +622,7 @@ public class XmlParser extends BaseParser /* implements IParser */ {
throw new ConfigurationException("Unable to encode extension, unrecognized child element type: " + value.getClass().getCanonicalName());
}
}
encodeChildElementToStreamWriter(theResource, theEventWriter, value, childName, childDef, null, theIncludedResource, null, theEncodeContext);
encodeChildElementToStreamWriter(theResource, theEventWriter, extDef, value, childName, childDef, null, theIncludedResource, null, theEncodeContext);
}
// child extensions

View File

@ -19,26 +19,31 @@ package ca.uhn.fhir.jaxrs.server.util;
* limitations under the License.
* #L%
*/
import java.io.*;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider;
import ca.uhn.fhir.rest.api.*;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.IRestfulResponse;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.method.ResourceParameter;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.lang3.StringUtils;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* The JaxRsRequest is a jax-rs specific implementation of the RequestDetails.
@ -47,22 +52,133 @@ import ca.uhn.fhir.util.UrlUtil;
*/
public class JaxRsRequest extends RequestDetails {
private HttpHeaders myHeaders;
private String myResourceString;
private AbstractJaxRsProvider myServer;
private Map<String, Object> myAttributes = new HashMap<>();
/**
* Utility Constructor
*
* @param server the server
* @param resourceString the resource body
* @param requestType the request type
* @param restOperation the operation type
*/
public JaxRsRequest(AbstractJaxRsProvider server, String resourceString, RequestTypeEnum requestType,
RestOperationTypeEnum restOperation) {
this.myHeaders = server.getHeaders();
this.myResourceString = resourceString;
this.setRestOperationType(restOperation);
setServer(server);
setFhirServerBase(server.getBaseForServer());
setParameters(server.getParameters());
setRequestType(requestType);
}
@Override
protected byte[] getByteStreamRequestContents() {
return StringUtils.defaultString(myResourceString, "")
.getBytes(ResourceParameter.determineRequestCharset(this));
}
@Override
public Charset getCharset() {
String charset = null;
if (myHeaders.getMediaType() != null && myHeaders.getMediaType().getParameters() != null) {
charset = myHeaders.getMediaType().getParameters().get(MediaType.CHARSET_PARAMETER);
}
if (charset != null) {
return Charset.forName(charset);
} else {
return null;
}
}
@Override
public FhirContext getFhirContext() {
return myServer.getFhirContext();
}
@Override
public String getHeader(String headerKey) {
List<String> requestHeader = getHeaders(headerKey);
return requestHeader.isEmpty() ? null : requestHeader.get(0);
}
@Override
public List<String> getHeaders(String name) {
List<String> requestHeader = myHeaders.getRequestHeader(name);
return requestHeader == null ? Collections.<String>emptyList() : requestHeader;
}
@Override
public Object getAttribute(String theAttributeName) {
return myAttributes.get(theAttributeName);
}
@Override
public void setAttribute(String theAttributeName, Object theAttributeValue) {
myAttributes.put(theAttributeName, theAttributeValue);
}
@Override
public InputStream getInputStream() {
// not yet implemented
throw new UnsupportedOperationException();
}
@Override
public Reader getReader() throws IOException {
// not yet implemented
throw new UnsupportedOperationException();
}
@Override
public IRestfulResponse getResponse() {
if (super.getResponse() == null) {
setResponse(new JaxRsResponse(this));
}
return super.getResponse();
}
@Override
public AbstractJaxRsProvider getServer() {
return myServer;
}
/**
* Set the server
*
* @param theServer the server to set
*/
public void setServer(AbstractJaxRsProvider theServer) {
this.myServer = theServer;
}
@Override
public String getServerBaseForRequest() {
return getServer().getServerAddressStrategy().determineServerBase(null, null);
}
/**
* An implementation of the builder pattern for the JaxRsRequest
*/
public static class Builder {
private final String myResourceName;
private String myCompartment;
private String myId;
private RequestTypeEnum myRequestType;
private String myRequestUrl;
private String myResource;
private final String myResourceName;
private RestOperationTypeEnum myRestOperation;
private AbstractJaxRsProvider myServer;
private String myVersion;
/**
* Utility Constructor
*
* @param theServer the server
* @param theRequestType the request type
* @param theRestOperation the rest operation
@ -79,6 +195,7 @@ public class JaxRsRequest extends RequestDetails {
/**
* Create the jax-rs request
*
* @return the jax-rs request
*/
public JaxRsRequest build() {
@ -167,6 +284,7 @@ public class JaxRsRequest extends RequestDetails {
/**
* Set the compartment
*
* @param compartment the compartment
* @return the builder
*/
@ -177,6 +295,7 @@ public class JaxRsRequest extends RequestDetails {
/**
* Set the id
*
* @param id the resource id
* @return the builder
*/
@ -187,6 +306,7 @@ public class JaxRsRequest extends RequestDetails {
/**
* Set the resource
*
* @param resource the body contents of an http method
* @return the builder
*/
@ -197,6 +317,7 @@ public class JaxRsRequest extends RequestDetails {
/**
* Set the id version
*
* @param version the version of the resource
* @return the builder
*/
@ -205,101 +326,4 @@ public class JaxRsRequest extends RequestDetails {
return this;
}
}
private HttpHeaders myHeaders;
private String myResourceString;
private AbstractJaxRsProvider myServer;
/**
* Utility Constructor
* @param server the server
* @param resourceString the resource body
* @param requestType the request type
* @param restOperation the operation type
*/
public JaxRsRequest(AbstractJaxRsProvider server, String resourceString, RequestTypeEnum requestType,
RestOperationTypeEnum restOperation) {
this.myHeaders = server.getHeaders();
this.myResourceString = resourceString;
this.setRestOperationType(restOperation);
setServer(server);
setFhirServerBase(server.getBaseForServer());
setParameters(server.getParameters());
setRequestType(requestType);
}
@Override
protected byte[] getByteStreamRequestContents() {
return StringUtils.defaultString(myResourceString, "")
.getBytes(ResourceParameter.determineRequestCharset(this));
}
@Override
public Charset getCharset() {
String charset = null;
if(myHeaders.getMediaType() != null && myHeaders.getMediaType().getParameters() != null) {
charset = myHeaders.getMediaType().getParameters().get(MediaType.CHARSET_PARAMETER);
}
if(charset != null) {
return Charset.forName(charset);
} else {
return null;
}
}
@Override
public FhirContext getFhirContext() {
return myServer.getFhirContext();
}
@Override
public String getHeader(String headerKey) {
List<String> requestHeader = getHeaders(headerKey);
return requestHeader.isEmpty() ? null : requestHeader.get(0);
}
@Override
public List<String> getHeaders(String name) {
List<String> requestHeader = myHeaders.getRequestHeader(name);
return requestHeader == null ? Collections.<String> emptyList() : requestHeader;
}
@Override
public InputStream getInputStream() {
// not yet implemented
throw new UnsupportedOperationException();
}
@Override
public Reader getReader() throws IOException {
// not yet implemented
throw new UnsupportedOperationException();
}
@Override
public IRestfulResponse getResponse() {
if (super.getResponse() == null) {
setResponse(new JaxRsResponse(this));
}
return super.getResponse();
}
@Override
public AbstractJaxRsProvider getServer() {
return myServer;
}
@Override
public String getServerBaseForRequest() {
return getServer().getServerAddressStrategy().determineServerBase(null, null);
}
/**
* Set the server
* @param theServer the server to set
*/
public void setServer(AbstractJaxRsProvider theServer) {
this.myServer = theServer;
}
}

View File

@ -1165,6 +1165,22 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
}
@Test
public void testElements() throws IOException {
DiagnosticReport dr = new DiagnosticReport();
dr.setStatus(DiagnosticReport.DiagnosticReportStatus.FINAL);
dr.getCode().setText("CODE TEXT");
ourClient.create().resource(dr).execute();
HttpGet get = new HttpGet(ourServerBase + "/DiagnosticReport?_include=DiagnosticReport:result&_elements:exclude=DiagnosticReport&_elements=DiagnosticReport:status,Observation:value,Observation:code,Observation:subject&_pretty=true");
try (CloseableHttpResponse response = ourHttpClient.execute(get)) {
assertEquals(200, response.getStatusLine().getStatusCode());
String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
assertThat(output, not(containsString("<Diagn")));
ourLog.info(output);
}
}
@Test
public void testEmptySearch() {
Bundle responseBundle;

View File

@ -165,6 +165,20 @@ public abstract class RequestDetails {
myId = theId;
}
/**
* Returns the attribute map for this request. Attributes are a place for user-supplied
* objects of any type to be attached to an individual request. They can be used to pass information
* between interceptor methods.
*/
public abstract Object getAttribute(String theAttributeName);
/**
* Returns the attribute map for this request. Attributes are a place for user-supplied
* objects of any type to be attached to an individual request. They can be used to pass information
* between interceptor methods.
*/
public abstract void setAttribute(String theAttributeName, Object theAttributeValue);
/**
* Retrieves the body of the request as binary data. Either this method or {@link #getReader} may be called to read
* the body, not both.

View File

@ -93,13 +93,13 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
* context, in order to avoid a dependency on Servlet-API 3.0+
*/
public static final String SERVLET_CONTEXT_ATTRIBUTE = "ca.uhn.fhir.rest.server.RestfulServer.servlet_context";
private static final ExceptionHandlingInterceptor DEFAULT_EXCEPTION_HANDLER = new ExceptionHandlingInterceptor();
private static final Logger ourLog = LoggerFactory.getLogger(RestfulServer.class);
private static final long serialVersionUID = 1L;
/**
* Default value for {@link #setDefaultPreferReturn(PreferReturnEnum)}
*/
public static final PreferReturnEnum DEFAULT_PREFER_RETURN = PreferReturnEnum.REPRESENTATION;
private static final ExceptionHandlingInterceptor DEFAULT_EXCEPTION_HANDLER = new ExceptionHandlingInterceptor();
private static final Logger ourLog = LoggerFactory.getLogger(RestfulServer.class);
private static final long serialVersionUID = 1L;
private final List<IServerInterceptor> myInterceptors = new ArrayList<>();
private final List<Object> myPlainProviders = new ArrayList<>();
private final List<IResourceProvider> myResourceProviders = new ArrayList<>();
@ -499,6 +499,19 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
return myETagSupport;
}
/**
* Sets (enables/disables) the server support for ETags. Must not be <code>null</code>. Default is
* {@link #DEFAULT_ETAG_SUPPORT}
*
* @param theETagSupport The ETag support mode
*/
public void setETagSupport(ETagSupportEnum theETagSupport) {
if (theETagSupport == null) {
throw new NullPointerException("theETagSupport can not be null");
}
myETagSupport = theETagSupport;
}
@Override
public ElementsSupportEnum getElementsSupport() {
return myElementsSupport;
@ -514,19 +527,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
myElementsSupport = theElementsSupport;
}
/**
* Sets (enables/disables) the server support for ETags. Must not be <code>null</code>. Default is
* {@link #DEFAULT_ETAG_SUPPORT}
*
* @param theETagSupport The ETag support mode
*/
public void setETagSupport(ETagSupportEnum theETagSupport) {
if (theETagSupport == null) {
throw new NullPointerException("theETagSupport can not be null");
}
myETagSupport = theETagSupport;
}
/**
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain
* providers should generally use this context if one is needed, as opposed to
@ -933,21 +933,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
requestDetails.setFhirServerBase(fhirServerBase);
requestDetails.setCompleteUrl(completeUrl);
// String pagingAction = theRequest.getParameter(Constants.PARAM_PAGINGACTION);
// if (getPagingProvider() != null && isNotBlank(pagingAction)) {
// requestDetails.setRestOperationType(RestOperationTypeEnum.GET_PAGE);
// if (theRequestType != RequestTypeEnum.GET) {
// /*
// * We reconstruct the link-self URL using the request parameters, and this would break if the parameters came
// in using a POST. We could probably work around that but why bother unless
// * someone comes up with a reason for needing it.
// */
// throw new InvalidRequestException(getFhirContext().getLocalizer().getMessage(RestfulServer.class,
// "getPagesNonHttpGet"));
// }
// handlePagingRequest(requestDetails, theResponse, pagingAction);
// return;
// }
validateRequest(requestDetails);
BaseMethodBinding<?> resourceMethod = determineResourceMethod(requestDetails, requestPath);
@ -1049,6 +1035,26 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
}
}
protected void validateRequest(ServletRequestDetails theRequestDetails) {
String[] elements = theRequestDetails.getParameters().get(Constants.PARAM_ELEMENTS);
if (elements != null) {
for (String next : elements) {
if (next.indexOf(':') != -1) {
throw new InvalidRequestException("Invalid _elements value: \"" + next + "\"");
}
}
}
elements = theRequestDetails.getParameters().get(Constants.PARAM_ELEMENTS + Constants.PARAM_ELEMENTS_EXCLUDE_MODIFIER);
if (elements != null) {
for (String next : elements) {
if (next.indexOf(':') != -1) {
throw new InvalidRequestException("Invalid _elements value: \"" + next + "\"");
}
}
}
}
/**
* Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations,
* but subclasses may put initialization code in {@link #initialize()}, which is

View File

@ -26,6 +26,7 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -103,6 +104,18 @@ public class ServletRequestDetails extends RequestDetails {
return headers == null ? Collections.<String> emptyList() : Collections.list(getServletRequest().getHeaders(name));
}
@Override
public Object getAttribute(String theAttributeName) {
Validate.notBlank(theAttributeName, "theAttributeName must not be null or blank");
return getServletRequest().getAttribute(theAttributeName);
}
@Override
public void setAttribute(String theAttributeName, Object theAttributeValue) {
Validate.notBlank(theAttributeName, "theAttributeName must not be null or blank");
getServletRequest().setAttribute(theAttributeName, theAttributeValue);
}
@Override
public InputStream getInputStream() throws IOException {
return getServletRequest().getInputStream();

View File

@ -18,10 +18,7 @@ import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.*;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.*;
import java.io.IOException;
import java.util.Collection;
@ -30,7 +27,8 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
public class ElementsParamR4Test {
@ -42,6 +40,7 @@ public class ElementsParamR4Test {
private static Server ourServer;
private static Procedure ourNextProcedure;
private static RestfulServer ourServlet;
private static Observation ourNextObservation;
@Before
public void before() {
@ -50,6 +49,92 @@ public class ElementsParamR4Test {
ourServlet.setElementsSupport(new RestfulServer().getElementsSupport());
}
@Test
public void testElementsOnChoiceWithGenericName() throws IOException {
createObservationWithQuantity();
verifyXmlAndJson(
"http://localhost:" + ourPort + "/Observation?_elements=value,status",
bundle -> {
Observation obs = (Observation) bundle.getEntry().get(0).getResource();
assertEquals("SUBSETTED", obs.getMeta().getTag().get(0).getCode());
assertEquals(Observation.ObservationStatus.FINAL, obs.getStatus());
assertEquals("222", obs.getValueQuantity().getValueElement().getValueAsString());
assertEquals("mg", obs.getValueQuantity().getCode());
});
}
@Test
public void testElementsOnChoiceWithSpecificName() throws IOException {
createObservationWithQuantity();
verifyXmlAndJson(
"http://localhost:" + ourPort + "/Observation?_elements=valueQuantity,status",
bundle -> {
Observation obs = (Observation) bundle.getEntry().get(0).getResource();
assertEquals("SUBSETTED", obs.getMeta().getTag().get(0).getCode());
assertEquals(Observation.ObservationStatus.FINAL, obs.getStatus());
assertEquals("222", obs.getValueQuantity().getValueElement().getValueAsString());
assertEquals("mg", obs.getValueQuantity().getCode());
});
}
@Test
@Ignore
public void testElementsOnChoiceWithSpecificNameNotMatching() throws IOException {
createObservationWithQuantity();
verifyXmlAndJson(
"http://localhost:" + ourPort + "/Observation?_elements=valueString,status",
bundle -> {
Observation obs = (Observation) bundle.getEntry().get(0).getResource();
assertEquals("SUBSETTED", obs.getMeta().getTag().get(0).getCode());
assertEquals(Observation.ObservationStatus.FINAL, obs.getStatus());
assertEquals(null, obs.getValueQuantity());
});
}
@Test
public void testExcludeResources() throws IOException {
createProcedureWithLongChain();
verifyXmlAndJson(
"http://localhost:" + ourPort + "/Procedure?_include=*&_elements:exclude=Procedure,DiagnosticReport,*.meta",
bundle -> {
assertEquals(null, bundle.getEntry().get(0).getResource());
assertEquals(null, bundle.getEntry().get(1).getResource());
Observation obs = (Observation) bundle.getEntry().get(2).getResource();
assertEquals(true, obs.getMeta().isEmpty());
assertEquals(Observation.ObservationStatus.FINAL, obs.getStatus());
assertEquals(1, obs.getCode().getCoding().size());
assertEquals("STRING VALUE", obs.getValueStringType().getValue());
});
}
@Test
public void testInvalidInclude() throws IOException {
createProcedureWithLongChain();
EncodingEnum encodingEnum;
HttpGet httpGet;
encodingEnum = EncodingEnum.JSON;
httpGet = new HttpGet(("http://localhost:" + ourPort + "/Procedure?_include=*&_elements=DiagnosticReport:foo") + "&_pretty=true&_format=" + encodingEnum.getFormatContentType());
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(responseContent);
assertEquals(400, status.getStatusLine().getStatusCode());
}
}
private void createObservationWithQuantity() {
ourNextObservation = new Observation();
ourNextObservation.setId("Observation/123/_history/456");
ourNextObservation.setStatus(Observation.ObservationStatus.FINAL);
ourNextObservation.setSubject(new Reference("Patient/AAA"));
ourNextObservation.setValue(new Quantity()
.setValue(222)
.setCode("mg")
.setSystem("http://unitsofmeasure.org"));
}
@Test
public void testReadSummaryData() throws Exception {
verifyXmlAndJson(
@ -139,7 +224,6 @@ public class ElementsParamR4Test {
});
}
@Test
public void testMultiResourceElementsFilter() throws IOException {
createProcedureWithLongChain();
@ -297,6 +381,21 @@ public class ElementsParamR4Test {
}
}
public static class DummyObservationResourceProvider implements IResourceProvider {
@Override
public Class<? extends IBaseResource> getResourceType() {
return Observation.class;
}
@Search
public Observation search(@IncludeParam(allow = {"*"}) Collection<Include> theIncludes) {
return ourNextObservation;
}
}
public static class DummyProcedureResourceProvider implements IResourceProvider {
@Override
@ -358,6 +457,8 @@ public class ElementsParamR4Test {
ourServlet.registerProvider(new DummyPatientResourceProvider());
ourServlet.registerProvider(new DummyProcedureResourceProvider());
ourServlet.registerProvider(new DummyObservationResourceProvider());
ServletHolder servletHolder = new ServletHolder(ourServlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);

View File

@ -6,6 +6,17 @@
<title>HAPI FHIR Changelog</title>
</properties>
<body>
<release version="3.8.0" date="TBD" description="Hippo">
<action type="add">
In Servers that are configured to support extended mode
<![CDATA[<code>_elements</code>]]> parameters, it is now possible to
use the :exclude modifier to exclude entire resource types.
</action>
<action type="add">
RequestDetails now has methods called getAttribute and setAttribute that can
be used by interceptors to pass arbitrary data between requests.
</action>
</release>
<release version="3.7.0" date="2019-02-06" description="Gale">
<action type="add">
HAPI FHIR is now built using OpenJDK 11. Users are recommended to upgrade to this version
@ -544,7 +555,6 @@
date was corrected. Thanks Heinz-Dieter Conradi for the Pull Request!
</action>
</release>
<release version="3.5.0" date="2018-09-17">
<action type="add">
HAPI FHIR now supports JDK 9 and JDK 10, both for building HAPI FHIR
@ -6102,7 +6112,7 @@ Bundle bundle = client.search().forResource(Patient.class)
<![CDATA[<b>API CHANGE:</b>]]>: Most elements in the HAPI FHIR model contain
a getId() and setId() method. This method is confusing because it is only actually used
for IDREF elements (which are rare) but its name makes it easy to confuse with more
important identifiers. For this reason, these methods have been deprocated and replaced with
important identifiers. For this reason, these methods have been deprecated and replaced with
get/setElementSpecificId() methods. The old methods will be removed at some point. Resource
types are unchanged and retain their get/setId methods.
</action>