Improvements to elements parameter as well as adding attributes to
RequestDetails
This commit is contained in:
parent
0f8c8d18e2
commit
810f1ad969
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue