Linked resources in server now correctly returned
This commit is contained in:
parent
9ef670aae9
commit
d0edc03f14
|
@ -49,7 +49,7 @@
|
|||
</action>
|
||||
<action type="add">
|
||||
Transaction server method is now allowed to return an OperationOutcome in addition to the
|
||||
incoming resources. The public test server now does this in ordeer to return status information
|
||||
incoming resources. The public test server now does this in order to return status information
|
||||
about the transaction processing.
|
||||
</action>
|
||||
<action type="add">
|
||||
|
@ -67,6 +67,11 @@
|
|||
<action type="add">
|
||||
Added narrative generator template for OperationOutcome resource
|
||||
</action>
|
||||
<action type="fix">
|
||||
Date/time types did not correctly parse values in the format "yyyymmdd" (although the FHIR-defined format
|
||||
is "yyyy-mm-dd" anyhow, and this is correctly handled). Thanks to Jeffrey Ting of Systems Made Simple
|
||||
for reporting!
|
||||
</action>
|
||||
</release>
|
||||
<release version="0.5" date="2014-Jul-30">
|
||||
<action type="add">
|
||||
|
|
|
@ -34,6 +34,7 @@ import ca.uhn.fhir.parser.DataFormatException;
|
|||
public abstract class BaseRuntimeElementCompositeDefinition<T extends ICompositeElement> extends BaseRuntimeElementDefinition<T> {
|
||||
|
||||
private List<BaseRuntimeChildDefinition> myChildren = new ArrayList<BaseRuntimeChildDefinition>();
|
||||
private List<BaseRuntimeChildDefinition> myChildrenAndExtensions;
|
||||
private Map<String, BaseRuntimeChildDefinition> myNameToChild = new HashMap<String, BaseRuntimeChildDefinition>();
|
||||
|
||||
public BaseRuntimeElementCompositeDefinition(String theName, Class<? extends T> theImplementingClass) {
|
||||
|
@ -65,6 +66,10 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IComposite
|
|||
return myChildren;
|
||||
}
|
||||
|
||||
public List<BaseRuntimeChildDefinition> getChildrenAndExtension() {
|
||||
return myChildrenAndExtensions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sealAndInitialize(Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
|
||||
super.sealAndInitialize(theClassToElementDefinitions);
|
||||
|
@ -90,5 +95,12 @@ public abstract class BaseRuntimeElementCompositeDefinition<T extends IComposite
|
|||
|
||||
myChildren = Collections.unmodifiableList(myChildren);
|
||||
myNameToChild = Collections.unmodifiableMap(myNameToChild);
|
||||
|
||||
List<BaseRuntimeChildDefinition> children = new ArrayList<BaseRuntimeChildDefinition>();
|
||||
children.addAll(myChildren);
|
||||
children.addAll(getExtensionsModifier());
|
||||
children.addAll(getExtensionsNonModifier());
|
||||
myChildrenAndExtensions=Collections.unmodifiableList(children);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -179,7 +179,7 @@ public class FhirContext {
|
|||
* Create and return a new JSON parser.
|
||||
*
|
||||
* <p>
|
||||
* Performance Note: <b>This class is cheap</b> to create, and may be called once for every message being processed without incurring any performance penalty
|
||||
* Performance Note: <b>This method is cheap</b> to call, and may be called once for every message being processed without incurring any performance penalty
|
||||
* </p>
|
||||
*/
|
||||
public IParser newJsonParser() {
|
||||
|
@ -192,7 +192,7 @@ public class FhirContext {
|
|||
* href="http://hl7api.sourceforge.net/hapi-fhir/doc_rest_client.html">RESTful Client</a> documentation for more information on how to define this interface.
|
||||
*
|
||||
* <p>
|
||||
* Performance Note: <b>This class is cheap</b> to create, and may be called once for every message being processed without incurring any performance penalty
|
||||
* Performance Note: <b>This method is cheap</b> to call, and may be called once for every operation invocation without incurring any performance penalty
|
||||
* </p>
|
||||
*
|
||||
* @param theClientType
|
||||
|
@ -210,8 +210,9 @@ public class FhirContext {
|
|||
/**
|
||||
* Instantiates a new generic client. A generic client is able to perform any of the FHIR RESTful operations against a compliant server, but does not have methods defining the specific
|
||||
* functionality required (as is the case with {@link #newRestfulClient(Class, String) non-generic clients}).
|
||||
*
|
||||
* <p>
|
||||
* In most cases it is preferable to use the non-generic clients instead of this mechanism, but not always.
|
||||
* Performance Note: <b>This method is cheap</b> to call, and may be called once for every operation invocation without incurring any performance penalty
|
||||
* </p>
|
||||
*
|
||||
* @param theServerBase
|
||||
|
@ -227,10 +228,10 @@ public class FhirContext {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create and return a new JSON parser.
|
||||
* Create and return a new XML parser.
|
||||
*
|
||||
* <p>
|
||||
* Performance Note: <b>This class is cheap</b> to create, and may be called once for every message being processed without incurring any performance penalty
|
||||
* Performance Note: <b>This method is cheap</b> to call, and may be called once for every message being processed without incurring any performance penalty
|
||||
* </p>
|
||||
*/
|
||||
public IParser newXmlParser() {
|
||||
|
|
|
@ -37,8 +37,6 @@ 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.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
|
||||
public class Bundle extends BaseBundle /* implements IElement */{
|
||||
|
|
|
@ -31,6 +31,7 @@ import java.util.Calendar;
|
|||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.TimeZone;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
|
||||
|
@ -165,13 +166,19 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
|
|||
myValue = theValue;
|
||||
}
|
||||
|
||||
private Pattern ourYearPattern = Pattern.compile("[0-9]{4}");
|
||||
private Pattern ourYearDashMonthPattern = Pattern.compile("[0-9]{4}-[0-9]{2}");
|
||||
private Pattern ourYearDashMonthDashDayPattern = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}");
|
||||
private Pattern ourYearMonthPattern = Pattern.compile("[0-9]{4}[0-9]{2}");
|
||||
private Pattern ourYearMonthDayPattern = Pattern.compile("[0-9]{4}[0-9]{2}[0-9]{2}");
|
||||
|
||||
@Override
|
||||
public void setValueAsString(String theValue) throws DataFormatException {
|
||||
try {
|
||||
if (theValue == null) {
|
||||
myValue = null;
|
||||
clearTimeZone();
|
||||
} else if (theValue.length() == 4) {
|
||||
} else if (theValue.length() == 4 && ourYearPattern.matcher(theValue).matches()) {
|
||||
if (isPrecisionAllowed(YEAR)) {
|
||||
setValue((ourYearFormat).parse(theValue));
|
||||
setPrecision(YEAR);
|
||||
|
@ -179,7 +186,7 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
|
|||
} else {
|
||||
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support YEAR precision): " + theValue);
|
||||
}
|
||||
} else if (theValue.length() == 7) {
|
||||
} else if (theValue.length() == 7 && ourYearDashMonthPattern.matcher(theValue).matches()) {
|
||||
// E.g. 1984-01 (this is valid according to the spec)
|
||||
if (isPrecisionAllowed(MONTH)) {
|
||||
setValue((ourYearMonthFormat).parse(theValue));
|
||||
|
@ -188,16 +195,16 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
|
|||
} else {
|
||||
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support MONTH precision): " + theValue);
|
||||
}
|
||||
} else if (theValue.length() == 8) {
|
||||
} else if (theValue.length() == 8 && ourYearMonthDayPattern.matcher(theValue).matches()) {
|
||||
//Eg. 19840101 (allow this just to be lenient)
|
||||
if (isPrecisionAllowed(DAY)) {
|
||||
setValue((ourYearMonthDayNoDashesFormat).parse(theValue));
|
||||
setPrecision(MONTH);
|
||||
setPrecision(DAY);
|
||||
clearTimeZone();
|
||||
} else {
|
||||
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
|
||||
}
|
||||
} else if (theValue.length() == 10) {
|
||||
} else if (theValue.length() == 10 && ourYearDashMonthDashDayPattern.matcher(theValue).matches()) {
|
||||
// E.g. 1984-01-01 (this is valid according to the spec)
|
||||
if (isPrecisionAllowed(DAY)) {
|
||||
setValue((ourYearMonthDayFormat).parse(theValue));
|
||||
|
|
|
@ -74,7 +74,7 @@ public interface IParser {
|
|||
* specify a class which extends a built-in type (e.g. a custom
|
||||
* type extending the default Patient class)
|
||||
* @param theReader
|
||||
* The reader to parse inpou from
|
||||
* The reader to parse input from. Note that the Reader will not be closed by the parser upon completion.
|
||||
* @return A parsed resource
|
||||
* @throws DataFormatException
|
||||
* If the resource can not be parsed because the data is not
|
||||
|
@ -98,8 +98,28 @@ public interface IParser {
|
|||
*/
|
||||
<T extends IResource> T parseResource(Class<T> theResourceType, String theString) throws DataFormatException;
|
||||
|
||||
/**
|
||||
* Parses a resource
|
||||
*
|
||||
* @param theReader
|
||||
* The reader to parse input from. Note that the Reader will not be closed by the parser upon completion.
|
||||
* @return A parsed resource
|
||||
* @throws DataFormatException
|
||||
* If the resource can not be parsed because the data is not
|
||||
* recognized or invalid for any reason
|
||||
*/
|
||||
IResource parseResource(Reader theReader) throws ConfigurationException, DataFormatException;
|
||||
|
||||
/**
|
||||
* Parses a resource
|
||||
*
|
||||
* @param theString
|
||||
* The string to parse
|
||||
* @return A parsed resource
|
||||
* @throws DataFormatException
|
||||
* If the resource can not be parsed because the data is not
|
||||
* recognized or invalid for any reason
|
||||
*/
|
||||
IResource parseResource(String theMessageString) throws ConfigurationException, DataFormatException;
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package ca.uhn.fhir.rest.param;
|
||||
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
|
||||
/**
|
||||
* Base class for RESTful operation parameter types
|
||||
*/
|
||||
public class BaseParam implements IQueryParameterType {
|
||||
|
||||
private Boolean myMissing;
|
||||
|
||||
/**
|
||||
* If set to non-null value, indicates that this parameter has been populated with a "[name]:missing=true" or "[name]:missing=false" vale
|
||||
* instead of a normal value
|
||||
*/
|
||||
public Boolean getMissing() {
|
||||
return myMissing;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String getQueryParameterQualifier() {
|
||||
if (myMissing) {
|
||||
return Constants.PARAMQUALIFIER_MISSING;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getValueAsQueryToken() {
|
||||
if (myMissing != null) {
|
||||
return myMissing ? "true" : "false";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* If set to non-null value, indicates that this parameter has been populated with a "[name]:missing=true" or "[name]:missing=false" vale
|
||||
* instead of a normal value
|
||||
*/
|
||||
public void setMissing(Boolean theMissing) {
|
||||
myMissing = theMissing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValueAsQueryToken(String theQualifier, String theValue) {
|
||||
if (Constants.PARAMQUALIFIER_MISSING.equals(theQualifier)) {
|
||||
myMissing = "true".equals(theValue);
|
||||
} else {
|
||||
myMissing = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -36,6 +36,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|||
public class DateParam extends DateTimeDt implements IQueryParameterType, IQueryParameterOr<DateParam> {
|
||||
|
||||
private QuantityCompararatorEnum myComparator;
|
||||
private BaseParam myBase=new BaseParam();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -78,11 +79,17 @@ public class DateParam extends DateTimeDt implements IQueryParameterType, IQuery
|
|||
|
||||
@Override
|
||||
public String getQueryParameterQualifier() {
|
||||
if (myBase.getMissing()!=null) {
|
||||
return myBase.getQueryParameterQualifier();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueAsQueryToken() {
|
||||
if (myBase.getMissing()!=null) {
|
||||
return myBase.getValueAsQueryToken();
|
||||
}
|
||||
if (myComparator != null && getValue() != null) {
|
||||
return myComparator.getCode() + getValueAsString();
|
||||
} else if (myComparator == null && getValue() != null) {
|
||||
|
@ -112,6 +119,13 @@ public class DateParam extends DateTimeDt implements IQueryParameterType, IQuery
|
|||
|
||||
@Override
|
||||
public void setValueAsQueryToken(String theQualifier, String theValue) {
|
||||
myBase.setValueAsQueryToken(theQualifier, theValue);
|
||||
if (myBase.getMissing()!=null) {
|
||||
setValue(null);
|
||||
myComparator=null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (theValue.length() < 2) {
|
||||
throw new DataFormatException("Invalid qualified date parameter: " + theValue);
|
||||
}
|
||||
|
@ -141,8 +155,10 @@ public class DateParam extends DateTimeDt implements IQueryParameterType, IQuery
|
|||
|
||||
@Override
|
||||
public void setValuesAsQueryTokens(QualifiedParamList theParameters) {
|
||||
myBase.setMissing(null);
|
||||
myComparator = null;
|
||||
setValueAsString(null);
|
||||
|
||||
if (theParameters.size() == 1) {
|
||||
setValueAsString(theParameters.get(0));
|
||||
} else if (theParameters.size() > 1) {
|
||||
|
@ -159,6 +175,9 @@ public class DateParam extends DateTimeDt implements IQueryParameterType, IQuery
|
|||
b.append(myComparator.getCode());
|
||||
}
|
||||
b.append(getValueAsString());
|
||||
if (myBase.getMissing()!=null) {
|
||||
b.append(" missing=").append(myBase.getMissing());
|
||||
}
|
||||
b.append("]");
|
||||
return b.toString();
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ import ca.uhn.fhir.model.primitive.DecimalDt;
|
|||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.model.primitive.UriDt;
|
||||
|
||||
public class QuantityParam implements IQueryParameterType {
|
||||
public class QuantityParam extends BaseParam implements IQueryParameterType {
|
||||
|
||||
private boolean myApproximate;
|
||||
private QuantityDt myQuantity = new QuantityDt();
|
||||
|
@ -131,6 +131,7 @@ public class QuantityParam implements IQueryParameterType {
|
|||
}
|
||||
|
||||
private void clear() {
|
||||
setMissing(null);
|
||||
myQuantity.setComparator((BoundCodeDt<QuantityCompararatorEnum>) null);
|
||||
myQuantity.setCode((CodeDt) null);
|
||||
myQuantity.setSystem((UriDt) null);
|
||||
|
@ -162,6 +163,10 @@ public class QuantityParam implements IQueryParameterType {
|
|||
|
||||
@Override
|
||||
public String getValueAsQueryToken() {
|
||||
if (super.getMissing()!=null) {
|
||||
return super.getValueAsQueryToken();
|
||||
}
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
if (myQuantity.getComparator() != null) {
|
||||
b.append(ParameterUtil.escape(myQuantity.getComparator().getValue()));
|
||||
|
@ -248,6 +253,11 @@ public class QuantityParam implements IQueryParameterType {
|
|||
public void setValueAsQueryToken(String theQualifier, String theValue) {
|
||||
clear();
|
||||
|
||||
super.setValueAsQueryToken(theQualifier, theValue);
|
||||
if (getMissing()!=null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (theValue == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -290,6 +300,9 @@ public class QuantityParam implements IQueryParameterType {
|
|||
b.append("value", myQuantity.getValue().getValueAsString());
|
||||
b.append("system", myQuantity.getSystem().getValueAsString());
|
||||
b.append("units", myQuantity.getUnits().getValueAsString());
|
||||
if (getMissing()!=null) {
|
||||
b.append("missing", getMissing());
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import ca.uhn.fhir.model.primitive.IdDt;
|
|||
public class ReferenceParam extends IdDt implements IQueryParameterType {
|
||||
|
||||
private String myChain;
|
||||
private BaseParam myBase=new BaseParam();
|
||||
|
||||
public ReferenceParam() {
|
||||
}
|
||||
|
@ -58,6 +59,10 @@ public class ReferenceParam extends IdDt implements IQueryParameterType {
|
|||
|
||||
@Override
|
||||
public String getQueryParameterQualifier() {
|
||||
if (myBase.getMissing()!=null) {
|
||||
return myBase.getQueryParameterQualifier();
|
||||
}
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
if (isNotBlank(getResourceType())) {
|
||||
b.append(':');
|
||||
|
@ -75,6 +80,9 @@ public class ReferenceParam extends IdDt implements IQueryParameterType {
|
|||
|
||||
@Override
|
||||
public String getValueAsQueryToken() {
|
||||
if (myBase.getMissing()!=null) {
|
||||
return myBase.getValueAsQueryToken();
|
||||
}
|
||||
return getIdPart();
|
||||
}
|
||||
|
||||
|
@ -91,6 +99,13 @@ public class ReferenceParam extends IdDt implements IQueryParameterType {
|
|||
|
||||
@Override
|
||||
public void setValueAsQueryToken(String theQualifier, String theValue) {
|
||||
myBase.setValueAsQueryToken(theQualifier, theValue);
|
||||
if (myBase.getMissing()!=null) {
|
||||
myChain=null;
|
||||
setValue(null);
|
||||
return;
|
||||
}
|
||||
|
||||
String q = theQualifier;
|
||||
String resourceType = null;
|
||||
if (isNotBlank(q)) {
|
||||
|
|
|
@ -30,7 +30,7 @@ import ca.uhn.fhir.model.api.IQueryParameterType;
|
|||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
|
||||
public class StringParam implements IQueryParameterType {
|
||||
public class StringParam extends BaseParam implements IQueryParameterType {
|
||||
|
||||
private boolean myExact;
|
||||
private String myValue;
|
||||
|
@ -52,7 +52,7 @@ public class StringParam implements IQueryParameterType {
|
|||
if (isExact()) {
|
||||
return Constants.PARAMQUALIFIER_STRING_EXACT;
|
||||
} else {
|
||||
return null;
|
||||
return super.getQueryParameterQualifier();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
|
|||
import ca.uhn.fhir.model.primitive.UriDt;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
|
||||
public class TokenParam implements IQueryParameterType {
|
||||
public class TokenParam extends BaseParam implements IQueryParameterType {
|
||||
|
||||
private String mySystem;
|
||||
private boolean myText;
|
||||
|
|
|
@ -80,6 +80,7 @@ public class Constants {
|
|||
public static final String PARAM_SORT_DESC = "_sort:desc";
|
||||
public static final String PARAM_TAGS = "_tags";
|
||||
public static final String PARAM_VALIDATE = "_validate";
|
||||
public static final String PARAMQUALIFIER_MISSING = ":missing";
|
||||
public static final String PARAMQUALIFIER_STRING_EXACT = ":exact";
|
||||
public static final String PARAMQUALIFIER_TOKEN_TEXT = ":text";
|
||||
public static final int STATUS_HTTP_200_OK = 200;
|
||||
|
|
|
@ -581,13 +581,13 @@ public class RestfulServer extends HttpServlet {
|
|||
*/
|
||||
private int escapedLength(String theServletPath) {
|
||||
int delta = 0;
|
||||
for(int i =0;i<theServletPath.length();i++) {
|
||||
for (int i = 0; i < theServletPath.length(); i++) {
|
||||
char next = theServletPath.charAt(i);
|
||||
if (next == ' ') {
|
||||
delta = delta + 2;
|
||||
}
|
||||
}
|
||||
return theServletPath.length()+delta;
|
||||
return theServletPath.length() + delta;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -812,34 +812,47 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
|
||||
List<ResourceReferenceDt> references = theContext.newTerser().getAllPopulatedChildElementsOfType(next, ResourceReferenceDt.class);
|
||||
for (ResourceReferenceDt nextRef : references) {
|
||||
IResource nextRes = nextRef.getResource();
|
||||
if (nextRes != null) {
|
||||
if (nextRes.getId().hasIdPart()) {
|
||||
IdDt id = nextRes.getId().toVersionless();
|
||||
if (id.hasResourceType()==false) {
|
||||
String resName = theContext.getResourceDefinition(nextRes).getName();
|
||||
id = id.withResourceType(resName);
|
||||
do {
|
||||
List<IResource> addedResourcesThisPass = new ArrayList<IResource>();
|
||||
|
||||
for (ResourceReferenceDt nextRef : references) {
|
||||
IResource nextRes = nextRef.getResource();
|
||||
if (nextRes != null) {
|
||||
if (nextRes.getId().hasIdPart()) {
|
||||
IdDt id = nextRes.getId().toVersionless();
|
||||
if (id.hasResourceType() == false) {
|
||||
String resName = theContext.getResourceDefinition(nextRes).getName();
|
||||
id = id.withResourceType(resName);
|
||||
}
|
||||
|
||||
if (!addedResourceIds.contains(id)) {
|
||||
addedResourceIds.add(id);
|
||||
addedResourcesThisPass.add(nextRes);
|
||||
}
|
||||
|
||||
nextRef.setResource(null);
|
||||
nextRef.setReference(id);
|
||||
}
|
||||
|
||||
if (!addedResourceIds.contains(id)) {
|
||||
addedResourceIds.add(id);
|
||||
addedResources.add(nextRes);
|
||||
}
|
||||
|
||||
nextRef.setResource(null);
|
||||
nextRef.setReference(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Linked resources may themselves have linked resources
|
||||
references = new ArrayList<ResourceReferenceDt>();
|
||||
for (IResource iResource : addedResourcesThisPass) {
|
||||
List<ResourceReferenceDt> newReferences = theContext.newTerser().getAllPopulatedChildElementsOfType(iResource, ResourceReferenceDt.class);
|
||||
references.addAll(newReferences);
|
||||
}
|
||||
|
||||
addedResources.addAll(addedResourcesThisPass);
|
||||
|
||||
} while (references.isEmpty() == false);
|
||||
bundle.addResource(next, theContext, theServerBase);
|
||||
}
|
||||
|
||||
for (IResource next : addedResources) {
|
||||
bundle.addResource(next, theContext, theServerBase);
|
||||
}
|
||||
|
||||
|
||||
bundle.getTotalResults().setValue(theTotalResults);
|
||||
return bundle;
|
||||
}
|
||||
|
@ -1056,7 +1069,7 @@ public class RestfulServer extends HttpServlet {
|
|||
if (next.getId() == null || next.getId().isEmpty()) {
|
||||
if (!(next instanceof OperationOutcome)) {
|
||||
throw new InternalErrorException("Server method returned resource of type[" + next.getClass().getSimpleName() + "] with no ID specified (IResource#setId(IdDt) must be called)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1110,8 +1123,7 @@ public class RestfulServer extends HttpServlet {
|
|||
String fullId = theResource.getId().withServerBase(theServerBase, resName);
|
||||
theHttpResponse.addHeader(Constants.HEADER_CONTENT_LOCATION, fullId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (theResource instanceof Binary) {
|
||||
Binary bin = (Binary) theResource;
|
||||
if (isNotBlank(bin.getContentType())) {
|
||||
|
@ -1122,9 +1134,9 @@ public class RestfulServer extends HttpServlet {
|
|||
if (bin.getContent() == null || bin.getContent().length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
theHttpResponse.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;");
|
||||
|
||||
|
||||
theHttpResponse.setContentLength(bin.getContent().length);
|
||||
ServletOutputStream oos = theHttpResponse.getOutputStream();
|
||||
oos.write(bin.getContent());
|
||||
|
@ -1190,6 +1202,4 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -143,7 +143,7 @@ public class FhirTerser {
|
|||
case COMPOSITE_DATATYPE:
|
||||
case RESOURCE: {
|
||||
BaseRuntimeElementCompositeDefinition<?> childDef = (BaseRuntimeElementCompositeDefinition<?>) theDefinition;
|
||||
for (BaseRuntimeChildDefinition nextChild : childDef.getChildren()) {
|
||||
for (BaseRuntimeChildDefinition nextChild : childDef.getChildrenAndExtension()) {
|
||||
List<? extends IElement> values = nextChild.getAccessor().getValues(theElement);
|
||||
if (values != null) {
|
||||
for (IElement nextValue : values) {
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
<init-param>
|
||||
<description>A comma separated list of allowed headers when making a non simple CORS request.</description>
|
||||
<param-name>cors.allowed.headers</param-name>
|
||||
<param-value>X-FHIR-Starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
|
||||
<param-value>X-FHIR-Starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A comma separated list non-standard response headers that will be exposed to XHR2 object.</description>
|
||||
|
|
|
@ -70,23 +70,21 @@
|
|||
<section name="References in Server Code">
|
||||
|
||||
<p>
|
||||
In server code, you may wish to return "contained" resources. A simple way to do this
|
||||
is to add these resources directly to the reference field and ensure that the resource
|
||||
has no ID populated. When this condition is detected, HAPI will automatically create a local
|
||||
reference ID and add the resource to the "contained" section when it encodes the resource.
|
||||
In server code, you will often want to return a resource which contains
|
||||
a link to another resource. Generally these "linked" resources are
|
||||
not actually included in the response, but rather a link to the
|
||||
resource is included and the client may request that resource directly
|
||||
(by ID) if it is needed.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The following example shows a Patient resource being created which will have a
|
||||
contained resource as its managing organization when encoded from a server:
|
||||
link to its managing organization when encoded from a server:
|
||||
</p>
|
||||
<source><![CDATA[Organization containedOrg = new Organization();
|
||||
containedOrg.getName().setValue("Contained Test Organization");
|
||||
|
||||
Patient patient = new Patient();
|
||||
<source><![CDATA[Patient patient = new Patient();
|
||||
patient.setId("Patient/1333");
|
||||
patient.addIdentifier("urn:mrns", "253345");
|
||||
patient.getManagingOrganization().setResource(patient);]]></source>
|
||||
patient.getManagingOrganization().setReference("Organization/124362");]]></source>
|
||||
|
||||
<subsection name="Handling Includes (_include) in a Bundle">
|
||||
|
||||
|
@ -111,6 +109,21 @@ patient.getManagingOrganization().setResource(patient);]]></source>
|
|||
org.setId("Organization/65546");
|
||||
org.getName().setValue("Contained Test Organization");
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/1333");
|
||||
patient.addIdentifier("urn:mrns", "253345");
|
||||
patient.getManagingOrganization().setResource(patient);]]></source>
|
||||
|
||||
<p>
|
||||
On the other hand, if the linked resource
|
||||
does not have an ID set, the linked resource will
|
||||
be included in the returned bundle as a "contained" resource. In this
|
||||
case, HAPI itself will define a local reference ID (e.g. "#001").
|
||||
</p>
|
||||
<source><![CDATA[Organization org = new Organization();
|
||||
// org.setId("Organization/65546");
|
||||
org.getName().setValue("Contained Test Organization");
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/1333");
|
||||
patient.addIdentifier("urn:mrns", "253345");
|
||||
|
|
|
@ -34,6 +34,12 @@ public class BaseDateTimeDtTest {
|
|||
assertEquals(TemporalPrecisionEnum.YEAR, dt.getPrecision());
|
||||
}
|
||||
|
||||
@Test()
|
||||
public void testParseMalformatted() throws DataFormatException {
|
||||
DateTimeDt dt = new DateTimeDt("20120102");
|
||||
assertEquals("2012-01-02",dt.getValueAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseMonth() throws DataFormatException {
|
||||
DateTimeDt dt = new DateTimeDt();
|
||||
|
|
|
@ -35,7 +35,6 @@ public class IdDtTest {
|
|||
public void testBigDecimalIds() {
|
||||
|
||||
IdDt id = new IdDt(new BigDecimal("123"));
|
||||
assertEquals(id.asBigDecimal(), new BigDecimal("123"));
|
||||
assertEquals(id.getIdPartAsBigDecimal(), new BigDecimal("123"));
|
||||
|
||||
}
|
||||
|
|
|
@ -25,10 +25,14 @@ import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.Bundle;
|
||||
import ca.uhn.fhir.model.api.ExtensionDt;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
|
||||
import ca.uhn.fhir.model.dstu.resource.Conformance;
|
||||
import ca.uhn.fhir.model.dstu.resource.Organization;
|
||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.IncludeTest;
|
||||
import ca.uhn.fhir.rest.server.IncludeTest.ExtPatient;
|
||||
|
||||
public class IncludedResourceStitchingClientTest {
|
||||
|
||||
|
@ -75,6 +79,113 @@ public class IncludedResourceStitchingClientTest {
|
|||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWithDeclaredExtension() throws Exception {
|
||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
|
||||
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_ATOM_XML + "; charset=UTF-8"));
|
||||
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(createLinkedBundle()), Charset.forName("UTF-8")));
|
||||
|
||||
IGenericClient client = ctx.newRestfulGenericClient( "http://foo");
|
||||
Bundle bundle = client.search().forResource(IncludeTest.ExtPatient.class).execute();
|
||||
|
||||
assertEquals(HttpGet.class, capt.getValue().getClass());
|
||||
HttpGet get = (HttpGet) capt.getValue();
|
||||
assertEquals("http://foo/Patient", get.getURI().toString());
|
||||
|
||||
assertEquals(4, bundle.size());
|
||||
|
||||
ExtPatient p = (ExtPatient) bundle.getEntries().get(0).getResource();
|
||||
ResourceReferenceDt ref = (ResourceReferenceDt) p.getSecondOrg();
|
||||
assertEquals("Organization/o1", ref.getReference().getValue());
|
||||
assertNotNull(ref.getResource());
|
||||
|
||||
Organization o1 = (Organization) ref.getResource();
|
||||
assertEquals("o2", o1.getPartOf().getReference().toUnqualifiedVersionless().getIdPart());
|
||||
assertNotNull(o1.getPartOf().getResource());
|
||||
|
||||
}
|
||||
|
||||
private String createLinkedBundle() {
|
||||
//@formatter:off
|
||||
return "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n" +
|
||||
" <title/>\n" +
|
||||
" <id>6cfcd90e-877a-40c6-a11c-448006712979</id>\n" +
|
||||
" <link rel=\"self\" href=\"http://localhost:49782/Patient?_query=declaredExtInclude&_pretty=true\"/>\n" +
|
||||
" <link rel=\"fhir-base\" href=\"http://localhost:49782\"/>\n" +
|
||||
" <os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">2</os:totalResults>\n" +
|
||||
" <published>2014-08-12T10:22:19.097-04:00</published>\n" +
|
||||
" <author>\n" +
|
||||
" <name>HAPI FHIR Server</name>\n" +
|
||||
" </author>\n" +
|
||||
" <entry>\n" +
|
||||
" <title>Patient p1</title>\n" +
|
||||
" <id>http://localhost:49782/Patient/p1</id>\n" +
|
||||
" <published>2014-08-12T10:22:19-04:00</published>\n" +
|
||||
" <link rel=\"self\" href=\"http://localhost:49782/Patient/p1\"/>\n" +
|
||||
" <content type=\"text/xml\">\n" +
|
||||
" <Patient xmlns=\"http://hl7.org/fhir\">\n" +
|
||||
" <extension url=\"http://foo#secondOrg\">\n" +
|
||||
" <valueResource>\n" +
|
||||
" <reference value=\"Organization/o1\"/>\n" +
|
||||
" </valueResource>\n" +
|
||||
" </extension>\n" +
|
||||
" <identifier>\n" +
|
||||
" <label value=\"p1\"/>\n" +
|
||||
" </identifier>\n" +
|
||||
" </Patient>\n" +
|
||||
" </content>\n" +
|
||||
" </entry>\n" +
|
||||
" <entry>\n" +
|
||||
" <title>Patient p2</title>\n" +
|
||||
" <id>http://localhost:49782/Patient/p2</id>\n" +
|
||||
" <published>2014-08-12T10:22:19-04:00</published>\n" +
|
||||
" <link rel=\"self\" href=\"http://localhost:49782/Patient/p2\"/>\n" +
|
||||
" <content type=\"text/xml\">\n" +
|
||||
" <Patient xmlns=\"http://hl7.org/fhir\">\n" +
|
||||
" <extension url=\"http://foo#secondOrg\">\n" +
|
||||
" <valueResource>\n" +
|
||||
" <reference value=\"Organization/o1\"/>\n" +
|
||||
" </valueResource>\n" +
|
||||
" </extension>\n" +
|
||||
" <identifier>\n" +
|
||||
" <label value=\"p2\"/>\n" +
|
||||
" </identifier>\n" +
|
||||
" </Patient>\n" +
|
||||
" </content>\n" +
|
||||
" </entry>\n" +
|
||||
" <entry>\n" +
|
||||
" <title>Organization o1</title>\n" +
|
||||
" <id>http://localhost:49782/Organization/o1</id>\n" +
|
||||
" <published>2014-08-12T10:22:19-04:00</published>\n" +
|
||||
" <link rel=\"self\" href=\"http://localhost:49782/Organization/o1\"/>\n" +
|
||||
" <content type=\"text/xml\">\n" +
|
||||
" <Organization xmlns=\"http://hl7.org/fhir\">\n" +
|
||||
" <name value=\"o1\"/>\n" +
|
||||
" <partOf>\n" +
|
||||
" <reference value=\"Organization/o2\"/>\n" +
|
||||
" </partOf>\n" +
|
||||
" </Organization>\n" +
|
||||
" </content>\n" +
|
||||
" </entry>\n" +
|
||||
" <entry>\n" +
|
||||
" <title>Organization o2</title>\n" +
|
||||
" <id>http://localhost:49782/Organization/o2</id>\n" +
|
||||
" <published>2014-08-12T10:22:19-04:00</published>\n" +
|
||||
" <link rel=\"self\" href=\"http://localhost:49782/Organization/o2\"/>\n" +
|
||||
" <content type=\"text/xml\">\n" +
|
||||
" <Organization xmlns=\"http://hl7.org/fhir\">\n" +
|
||||
" <name value=\"o2\"/>\n" +
|
||||
" </Organization>\n" +
|
||||
" </content>\n" +
|
||||
" </entry>\n" +
|
||||
"</feed>";
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
|
||||
private String createBundle() {
|
||||
//@formatter:on
|
||||
return "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n" +
|
||||
|
|
|
@ -25,6 +25,9 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import ca.uhn.fhir.model.api.Bundle;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.model.api.annotation.Child;
|
||||
import ca.uhn.fhir.model.api.annotation.Extension;
|
||||
import ca.uhn.fhir.model.api.annotation.ResourceDef;
|
||||
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
|
||||
import ca.uhn.fhir.model.dstu.resource.Organization;
|
||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||
|
@ -34,6 +37,7 @@ import ca.uhn.fhir.rest.annotation.IncludeParam;
|
|||
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.testutil.RandomServerPortProvider;
|
||||
import ca.uhn.fhir.util.ElementUtil;
|
||||
|
||||
/**
|
||||
* Created by dsotnikov on 2/25/2014.
|
||||
|
@ -157,6 +161,34 @@ public class IncludeTest {
|
|||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIIncludedResourcesNonContainedInDeclaredExtension() throws Exception {
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=declaredExtInclude&_pretty=true");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent);
|
||||
|
||||
ourLog.info(responseContent);
|
||||
|
||||
assertEquals(4, bundle.size());
|
||||
assertEquals(new IdDt("Patient/p1"), bundle.toListOfResources().get(0).getId().toUnqualifiedVersionless());
|
||||
assertEquals(new IdDt("Patient/p2"), bundle.toListOfResources().get(1).getId().toUnqualifiedVersionless());
|
||||
assertEquals(new IdDt("Organization/o1"), bundle.toListOfResources().get(2).getId().toUnqualifiedVersionless());
|
||||
assertEquals(new IdDt("Organization/o2"), bundle.toListOfResources().get(3).getId().toUnqualifiedVersionless());
|
||||
|
||||
Patient p1 = (Patient) bundle.toListOfResources().get(0);
|
||||
assertEquals(0,p1.getContained().getContainedResources().size());
|
||||
|
||||
Patient p2 = (Patient) bundle.toListOfResources().get(1);
|
||||
assertEquals(0,p2.getContained().getContainedResources().size());
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
|
@ -214,6 +246,32 @@ public class IncludeTest {
|
|||
|
||||
}
|
||||
|
||||
@ResourceDef(name="Patient")
|
||||
public static class ExtPatient extends Patient
|
||||
{
|
||||
@Child(name="secondOrg")
|
||||
@Extension(url="http://foo#secondOrg", definedLocally = false, isModifier = false)
|
||||
private ResourceReferenceDt mySecondOrg;
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return super.isEmpty() && ElementUtil.isEmpty(mySecondOrg);
|
||||
}
|
||||
|
||||
public ResourceReferenceDt getSecondOrg() {
|
||||
if (mySecondOrg==null) {
|
||||
mySecondOrg= new ResourceReferenceDt();
|
||||
}
|
||||
return mySecondOrg;
|
||||
}
|
||||
|
||||
public void setSecondOrg(ResourceReferenceDt theSecondOrg) {
|
||||
mySecondOrg = theSecondOrg;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Created by dsotnikov on 2/25/2014.
|
||||
*/
|
||||
|
@ -238,6 +296,7 @@ public class IncludeTest {
|
|||
return Arrays.asList(p1, p2);
|
||||
}
|
||||
|
||||
|
||||
@Search(queryName = "extInclude")
|
||||
public List<Patient> extInclude() {
|
||||
Organization o1 = new Organization();
|
||||
|
@ -257,6 +316,31 @@ public class IncludeTest {
|
|||
return Arrays.asList(p1, p2);
|
||||
}
|
||||
|
||||
@Search(queryName = "declaredExtInclude")
|
||||
public List<ExtPatient> declaredExtInclude() {
|
||||
Organization o1 = new Organization();
|
||||
o1.getName().setValue("o1");
|
||||
o1.setId("o1");
|
||||
|
||||
Organization o2 = new Organization();
|
||||
o2.getName().setValue("o2");
|
||||
o2.setId("o2");
|
||||
o1.getPartOf().setResource(o2);
|
||||
|
||||
ExtPatient p1 = new ExtPatient();
|
||||
p1.setId("p1");
|
||||
p1.addIdentifier().setLabel("p1");
|
||||
p1.getSecondOrg().setResource(o1);
|
||||
|
||||
ExtPatient p2 = new ExtPatient();
|
||||
p2.setId("p2");
|
||||
p2.addIdentifier().setLabel("p2");
|
||||
p2.getSecondOrg().setResource(o1);
|
||||
|
||||
return Arrays.asList(p1, p2);
|
||||
}
|
||||
|
||||
|
||||
@Search(queryName = "containedInclude")
|
||||
public List<Patient> containedInclude() {
|
||||
Organization o1 = new Organization();
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<dependent-module archiveName="hapi-fhir-base-0.6-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base">
|
||||
<dependency-type>uses</dependency-type>
|
||||
</dependent-module>
|
||||
<dependent-module deploy-path="/" handle="module:/overlay/var/M2_REPO/ca/uhn/hapi/fhir/hapi-fhir-testpage-overlay/0.6-SNAPSHOT/hapi-fhir-testpage-overlay-0.6-SNAPSHOT.war?unpackFolder=target/m2e-wtp/overlays&includes=**/**&excludes=META-INF/MANIFEST.MF">
|
||||
<dependent-module deploy-path="/" handle="module:/overlay/prj/hapi-fhir-testpage-overlay?includes=**/**&excludes=META-INF/MANIFEST.MF">
|
||||
<dependency-type>consumes</dependency-type>
|
||||
</dependent-module>
|
||||
<dependent-module deploy-path="/" handle="module:/overlay/slf/?includes=**/**&excludes=META-INF/MANIFEST.MF">
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<dependent-module archiveName="hapi-fhir-base-0.6-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base">
|
||||
<dependency-type>uses</dependency-type>
|
||||
</dependent-module>
|
||||
<dependent-module deploy-path="/" handle="module:/overlay/var/M2_REPO/ca/uhn/hapi/fhir/hapi-fhir-testpage-overlay/0.6-SNAPSHOT/hapi-fhir-testpage-overlay-0.6-SNAPSHOT.war?unpackFolder=target/m2e-wtp/overlays&includes=**/**&excludes=META-INF/MANIFEST.MF">
|
||||
<dependent-module deploy-path="/" handle="module:/overlay/prj/hapi-fhir-testpage-overlay?includes=**/**&excludes=META-INF/MANIFEST.MF">
|
||||
<dependency-type>consumes</dependency-type>
|
||||
</dependent-module>
|
||||
<dependent-module deploy-path="/" handle="module:/overlay/slf/?includes=**/**&excludes=META-INF/MANIFEST.MF">
|
||||
|
|
Loading…
Reference in New Issue