This commit is contained in:
lmds1 2014-11-19 15:48:20 -05:00
commit 57b34cd869
49 changed files with 2270 additions and 1481 deletions

View File

@ -23,22 +23,12 @@ package ca.uhn.fhir.model.api;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import ca.uhn.fhir.parser.DataFormatException;
public abstract class BasePrimitive<T> extends BaseIdentifiableElement implements IPrimitiveDatatype<T> {
@Override
public boolean isEmpty() {
return super.isBaseEmpty() && getValue() == null;
}
@Override
public String toString() {
return getClass().getSimpleName() + "[" + getValueAsString() + "]";
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(getValue()).toHashCode();
}
private T myCoercedValue;
private String myStringValue;
@Override
public boolean equals(Object theObj) {
@ -55,4 +45,73 @@ public abstract class BasePrimitive<T> extends BaseIdentifiableElement implement
b.append(getValue(), o.getValue());
return b.isEquals();
}
@Override
public T getValue() {
return myCoercedValue;
}
@Override
public String getValueAsString() throws DataFormatException {
return myStringValue;
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(getValue()).toHashCode();
}
@Override
public boolean isEmpty() {
return super.isBaseEmpty() && getValue() == null;
}
@Override
public void setValue(T theValue) throws DataFormatException {
myCoercedValue = theValue;
updateStringValue();
}
protected void updateStringValue() {
if (myCoercedValue == null) {
myStringValue = null;
} else {
// NB this might be null
myStringValue = encode(myCoercedValue);
}
}
@Override
public void setValueAsString(String theValue) throws DataFormatException {
if (theValue == null) {
myCoercedValue = null;
} else {
// NB this might be null
myCoercedValue = parse(theValue);
}
myStringValue = theValue;
}
/**
* Subclasses must override to convert an encoded representation of this datatype into a "coerced" one
*
* @param theValue
* Will not be null
* @return May return null if the value does not correspond to anything
*/
protected abstract T parse(String theValue);
/**
* Subclasses must override to convert a "coerced" value into an encoded one.
*
* @param theValue
* Will not be null
* @return May return null if the value does not correspond to anything
*/
protected abstract String encode(T theValue);
@Override
public String toString() {
return getClass().getSimpleName() + "[" + getValueAsString() + "]";
}
}

View File

@ -117,56 +117,63 @@ public class Bundle extends BaseBundle /* implements IElement */{
entry.getTitle().setValue(def.getName() + " " + StringUtils.defaultString(theResource.getId().getValue(), "(no ID)"));
}
if (theResource.getId() != null && StringUtils.isNotBlank(theResource.getId().getValue())) {
if (theResource.getId() != null) {
if (theResource.getId().isAbsolute()) {
entry.getLinkSelf().setValue(theResource.getId().getValue());
entry.getId().setValue(theResource.getId().toVersionless().getValue());
} else if (StringUtils.isNotBlank(theResource.getId().getValue())) {
StringBuilder b = new StringBuilder();
b.append(theServerBase);
if (b.length() > 0 && b.charAt(b.length() - 1) != '/') {
StringBuilder b = new StringBuilder();
b.append(theServerBase);
if (b.length() > 0 && b.charAt(b.length() - 1) != '/') {
b.append('/');
}
b.append(def.getName());
b.append('/');
}
b.append(def.getName());
b.append('/');
String resId = theResource.getId().getIdPart();
b.append(resId);
String resId = theResource.getId().getIdPart();
b.append(resId);
entry.getId().setValue(b.toString());
entry.getId().setValue(b.toString());
if (isNotBlank(theResource.getId().getVersionIdPart())) {
b.append('/');
b.append(Constants.PARAM_HISTORY);
b.append('/');
b.append(theResource.getId().getVersionIdPart());
} else {
IdDt versionId = (IdDt) ResourceMetadataKeyEnum.VERSION_ID.get(theResource);
if (versionId != null) {
if (isNotBlank(theResource.getId().getVersionIdPart())) {
b.append('/');
b.append(Constants.PARAM_HISTORY);
b.append('/');
b.append(versionId.getValue());
b.append(theResource.getId().getVersionIdPart());
} else {
IdDt versionId = (IdDt) ResourceMetadataKeyEnum.VERSION_ID.get(theResource);
if (versionId != null) {
b.append('/');
b.append(Constants.PARAM_HISTORY);
b.append('/');
b.append(versionId.getValue());
}
}
}
String qualifiedId = b.toString();
entry.getLinkSelf().setValue(qualifiedId);
String qualifiedId = b.toString();
entry.getLinkSelf().setValue(qualifiedId);
// String resourceType = theContext.getResourceDefinition(theResource).getName();
// String resourceType = theContext.getResourceDefinition(theResource).getName();
String linkSearch = ResourceMetadataKeyEnum.LINK_SEARCH.get(theResource);
if (isNotBlank(linkSearch)) {
if (!UrlUtil.isAbsolute(linkSearch)) {
linkSearch = (theServerBase + "/" + linkSearch);
String linkSearch = ResourceMetadataKeyEnum.LINK_SEARCH.get(theResource);
if (isNotBlank(linkSearch)) {
if (!UrlUtil.isAbsolute(linkSearch)) {
linkSearch = (theServerBase + "/" + linkSearch);
}
entry.getLinkSearch().setValue(linkSearch);
}
entry.getLinkSearch().setValue(linkSearch);
}
String linkAlternate = ResourceMetadataKeyEnum.LINK_ALTERNATE.get(theResource);
if (isNotBlank(linkAlternate)) {
if (!UrlUtil.isAbsolute(linkAlternate)) {
linkSearch = (theServerBase + "/" + linkAlternate);
String linkAlternate = ResourceMetadataKeyEnum.LINK_ALTERNATE.get(theResource);
if (isNotBlank(linkAlternate)) {
if (!UrlUtil.isAbsolute(linkAlternate)) {
linkSearch = (theServerBase + "/" + linkAlternate);
}
entry.getLinkAlternate().setValue(linkSearch);
}
entry.getLinkAlternate().setValue(linkSearch);
}
}
}
InstantDt published = ResourceMetadataKeyEnum.PUBLISHED.get(theResource);

View File

@ -22,11 +22,23 @@ package ca.uhn.fhir.model.api;
import java.util.Map;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.dstu.composite.ContainedDt;
import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
import ca.uhn.fhir.model.primitive.CodeDt;
import ca.uhn.fhir.model.primitive.IdDt;
/**
* This interface is the parent interface for all FHIR Resource definition
* classes. Classes implementing this interface should be annotated
* with the {@link ResourceDef @ResourceDef} annotation.
*
* <p>
* Note that this class is a part of HAPI's model API, used to define
* structure classes. Users will often interact with this interface, but
* should not need to implement it directly.
* <p>
*/
public interface IResource extends ICompositeElement {
/**
@ -113,8 +125,11 @@ public interface IResource extends ICompositeElement {
void setResourceMetadata(Map<ResourceMetadataKeyEnum<?>, Object> theMap);
/**
* Returns a String representing the name of this Resource
* @return the name of this Resource
* Returns a String representing the name of this Resource. This return
* value is not used for anything by HAPI itself, but is provided as a
* convenience to developers using the API.
*
* @return the name of this resource, e.g. "Patient", or "Observation"
*/
String getResourceName();

View File

@ -29,8 +29,6 @@ import ca.uhn.fhir.model.api.annotation.SimpleSetter;
@DatatypeDef(name = "base64Binary")
public class Base64BinaryDt extends BasePrimitive<byte[]> {
private byte[] myValue;
/**
* Constructor
*/
@ -42,36 +40,18 @@ public class Base64BinaryDt extends BasePrimitive<byte[]> {
* Constructor
*/
@SimpleSetter
public Base64BinaryDt(@SimpleSetter.Parameter(name="theBytes") byte[] theBytes) {
public Base64BinaryDt(@SimpleSetter.Parameter(name = "theBytes") byte[] theBytes) {
setValue(theBytes);
}
@Override
public void setValueAsString(String theValue) {
if (theValue == null) {
myValue = null;
} else {
myValue = Base64.decodeBase64(theValue);
}
protected byte[] parse(String theValue) {
return Base64.decodeBase64(theValue);
}
@Override
public String getValueAsString() {
if (myValue == null) {
return null;
} else {
return Base64.encodeBase64String(myValue);
}
}
@Override
public void setValue(byte[] theValue) {
myValue = theValue;
}
@Override
public byte[] getValue() {
return myValue;
protected String encode(byte[] theValue) {
return Base64.encodeBase64String(theValue);
}
}

View File

@ -23,9 +23,12 @@ package ca.uhn.fhir.model.primitive;
import static ca.uhn.fhir.model.api.TemporalPrecisionEnum.*;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
import java.util.regex.Pattern;
@ -39,6 +42,16 @@ import ca.uhn.fhir.parser.DataFormatException;
public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
/**
* For unit tests only
*/
static List<FastDateFormat> getFormatters() {
return ourFormatters;
}
/*
* Add any new formatters to the static block below!!
*/
private static final List<FastDateFormat> ourFormatters;
private static final Pattern ourYearDashMonthDashDayPattern = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}");
private static final Pattern ourYearDashMonthPattern = Pattern.compile("[0-9]{4}-[0-9]{2}");
private static final FastDateFormat ourYearFormat = FastDateFormat.getInstance("yyyy");
@ -47,80 +60,131 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
private static final Pattern ourYearMonthDayPattern = Pattern.compile("[0-9]{4}[0-9]{2}[0-9]{2}");
private static final FastDateFormat ourYearMonthDayTimeFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss");
private static final FastDateFormat ourYearMonthDayTimeMilliFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS");
private static final FastDateFormat ourYearMonthDayTimeUTCZFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("UTC"));
private static final FastDateFormat ourYearMonthDayTimeMilliUTCZFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone("UTC"));
private static final FastDateFormat ourYearMonthDayTimeMilliZoneFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ");
private static final FastDateFormat ourYearMonthDayTimeUTCZFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("UTC"));
private static final FastDateFormat ourYearMonthDayTimeZoneFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ssZZ");
private static final FastDateFormat ourYearMonthFormat = FastDateFormat.getInstance("yyyy-MM");
private static final FastDateFormat ourYearMonthNoDashesFormat = FastDateFormat.getInstance("yyyyMM");
private static final Pattern ourYearMonthPattern = Pattern.compile("[0-9]{4}[0-9]{2}");
private static final Pattern ourYearPattern = Pattern.compile("[0-9]{4}");
static {
ArrayList<FastDateFormat> formatters = new ArrayList<FastDateFormat>();
formatters.add(ourYearFormat);
formatters.add(ourYearMonthDayFormat);
formatters.add(ourYearMonthDayNoDashesFormat);
formatters.add(ourYearMonthDayTimeFormat);
formatters.add(ourYearMonthDayTimeMilliFormat);
formatters.add(ourYearMonthDayTimeUTCZFormat);
formatters.add(ourYearMonthDayTimeMilliUTCZFormat);
formatters.add(ourYearMonthDayTimeMilliZoneFormat);
formatters.add(ourYearMonthDayTimeZoneFormat);
formatters.add(ourYearMonthFormat);
formatters.add(ourYearMonthNoDashesFormat);
ourFormatters = Collections.unmodifiableList(formatters);
}
private TemporalPrecisionEnum myPrecision = TemporalPrecisionEnum.SECOND;
private TimeZone myTimeZone;
private boolean myTimeZoneZulu = false;
private Date myValue;
/**
* Gets the precision for this datatype using field values from {@link Calendar}, such as {@link Calendar#MONTH}.
* Default is {@link Calendar#DAY_OF_MONTH}
*
* @see #setPrecision(int)
*/
public TemporalPrecisionEnum getPrecision() {
return myPrecision;
}
public TimeZone getTimeZone() {
return myTimeZone;
private void clearTimeZone() {
myTimeZone = null;
myTimeZoneZulu = false;
}
@Override
public Date getValue() {
return myValue;
}
@Override
public String getValueAsString() {
if (myValue == null) {
protected String encode(Date theValue) {
if (theValue == null) {
return null;
} else {
switch (myPrecision) {
case DAY:
return ourYearMonthDayFormat.format(myValue);
return ourYearMonthDayFormat.format(theValue);
case MONTH:
return ourYearMonthFormat.format(myValue);
return ourYearMonthFormat.format(theValue);
case YEAR:
return ourYearFormat.format(myValue);
return ourYearFormat.format(theValue);
case SECOND:
if (myTimeZoneZulu) {
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
cal.setTime(myValue);
cal.setTime(theValue);
return ourYearMonthDayTimeFormat.format(cal) + "Z";
} else if (myTimeZone != null) {
GregorianCalendar cal = new GregorianCalendar(myTimeZone);
cal.setTime(myValue);
cal.setTime(theValue);
return ourYearMonthDayTimeZoneFormat.format(cal);
} else {
return ourYearMonthDayTimeFormat.format(myValue);
return ourYearMonthDayTimeFormat.format(theValue);
}
case MILLI:
if (myTimeZoneZulu) {
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
cal.setTime(myValue);
cal.setTime(theValue);
return ourYearMonthDayTimeMilliFormat.format(cal) + "Z";
} else if (myTimeZone != null) {
GregorianCalendar cal = new GregorianCalendar(myTimeZone);
cal.setTime(myValue);
cal.setTime(theValue);
return ourYearMonthDayTimeMilliZoneFormat.format(cal);
} else {
return ourYearMonthDayTimeMilliFormat.format(myValue);
return ourYearMonthDayTimeMilliFormat.format(theValue);
}
}
throw new IllegalStateException("Invalid precision (this is a HAPI bug, shouldn't happen): " + myPrecision);
}
}
/**
* Gets the precision for this datatype (using the default for the given type if not set)
*
* @see #setPrecision(TemporalPrecisionEnum)
*/
public TemporalPrecisionEnum getPrecision() {
if (myPrecision == null) {
return getDefaultPrecisionForDatatype();
}
return myPrecision;
}
/**
* Returns the default precision for the given datatype
*/
protected abstract TemporalPrecisionEnum getDefaultPrecisionForDatatype();
/**
* Returns the TimeZone associated with this dateTime's value. May return
* <code>null</code> if no timezone was supplied.
*/
public TimeZone getTimeZone() {
return myTimeZone;
}
private boolean hasOffset(String theValue) {
boolean inTime = false;
for (int i = 0; i < theValue.length(); i++) {
switch (theValue.charAt(i)) {
case 'T':
inTime = true;
break;
case '+':
case '-':
if (inTime) {
return true;
}
break;
}
}
return false;
}
/**
* To be implemented by subclasses to indicate whether the given precision is allowed by this type
*/
abstract boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision);
public boolean isTimeZoneZulu() {
return myTimeZoneZulu;
}
@ -132,8 +196,105 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
* if {@link #getValue()} returns <code>null</code>
*/
public boolean isToday() {
Validate.notNull(myValue, getClass().getSimpleName() + " contains null value");
return DateUtils.isSameDay(new Date(), myValue);
Validate.notNull(getValue(), getClass().getSimpleName() + " contains null value");
return DateUtils.isSameDay(new Date(), getValue());
}
@Override
protected Date parse(String theValue) throws DataFormatException {
try {
if (theValue.length() == 4 && ourYearPattern.matcher(theValue).matches()) {
if (isPrecisionAllowed(YEAR)) {
setPrecision(YEAR);
clearTimeZone();
return ((ourYearFormat).parse(theValue));
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support YEAR precision): " + theValue);
}
} else if (theValue.length() == 6 && ourYearMonthPattern.matcher(theValue).matches()) {
// Eg. 198401 (allow this just to be lenient)
if (isPrecisionAllowed(MONTH)) {
setPrecision(MONTH);
clearTimeZone();
return ((ourYearMonthNoDashesFormat).parse(theValue));
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
}
} else if (theValue.length() == 7 && ourYearDashMonthPattern.matcher(theValue).matches()) {
// E.g. 1984-01 (this is valid according to the spec)
if (isPrecisionAllowed(MONTH)) {
setPrecision(MONTH);
clearTimeZone();
return ((ourYearMonthFormat).parse(theValue));
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support MONTH precision): " + theValue);
}
} else if (theValue.length() == 8 && ourYearMonthDayPattern.matcher(theValue).matches()) {
// Eg. 19840101 (allow this just to be lenient)
if (isPrecisionAllowed(DAY)) {
setPrecision(DAY);
clearTimeZone();
return ((ourYearMonthDayNoDashesFormat).parse(theValue));
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
}
} else if (theValue.length() == 10 && ourYearDashMonthDashDayPattern.matcher(theValue).matches()) {
// E.g. 1984-01-01 (this is valid according to the spec)
if (isPrecisionAllowed(DAY)) {
setPrecision(DAY);
clearTimeZone();
return ((ourYearMonthDayFormat).parse(theValue));
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
}
} else if (theValue.length() >= 18) { // date and time with possible time zone
int dotIndex = theValue.indexOf('.', 18);
boolean hasMillis = dotIndex > -1;
if (!hasMillis && !isPrecisionAllowed(SECOND)) {
throw new DataFormatException("Invalid date/time string (data type does not support SECONDS precision): " + theValue);
} else if (hasMillis && !isPrecisionAllowed(MILLI)) {
throw new DataFormatException("Invalid date/time string (data type " + getClass().getSimpleName() + " does not support MILLIS precision):" + theValue);
}
Date retVal;
if (hasMillis) {
try {
if (hasOffset(theValue)) {
retVal = ourYearMonthDayTimeMilliZoneFormat.parse(theValue);
} else if (theValue.endsWith("Z"))
retVal = ourYearMonthDayTimeMilliUTCZFormat.parse(theValue);
else
retVal = ourYearMonthDayTimeMilliFormat.parse(theValue);
} catch (ParseException p2) {
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
}
setTimeZone(theValue, hasMillis);
setPrecision(TemporalPrecisionEnum.MILLI);
} else {
try {
if (hasOffset(theValue)) {
retVal = ourYearMonthDayTimeZoneFormat.parse(theValue);
} else if (theValue.endsWith("Z")) {
retVal = ourYearMonthDayTimeUTCZFormat.parse(theValue);
} else {
retVal = ourYearMonthDayTimeFormat.parse(theValue);
}
} catch (ParseException p2) {
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
}
setTimeZone(theValue, hasMillis);
setPrecision(TemporalPrecisionEnum.SECOND);
}
return retVal;
} else {
throw new DataFormatException("Invalid date/time string (invalid length): " + theValue);
}
} catch (ParseException e) {
throw new DataFormatException("Invalid date string (" + e.getMessage() + "): " + theValue);
}
}
/**
@ -152,141 +313,60 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
throw new NullPointerException("Precision may not be null");
}
myPrecision = thePrecision;
updateStringValue();
}
public void setTimeZone(TimeZone theTimeZone) {
myTimeZone = theTimeZone;
}
public void setTimeZoneZulu(boolean theTimeZoneZulu) {
myTimeZoneZulu = theTimeZoneZulu;
}
@Override
public void setValue(Date theValue) throws DataFormatException {
myValue = theValue;
}
@Override
public void setValueAsString(String theValue) throws DataFormatException {
try {
if (theValue == null) {
myValue = null;
clearTimeZone();
} else if (theValue.length() == 4 && ourYearPattern.matcher(theValue).matches()) {
if (isPrecisionAllowed(YEAR)) {
setValue((ourYearFormat).parse(theValue));
setPrecision(YEAR);
clearTimeZone();
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support YEAR precision): " + theValue);
}
} else if (theValue.length() == 6 && ourYearMonthPattern.matcher(theValue).matches()) {
// Eg. 198401 (allow this just to be lenient)
if (isPrecisionAllowed(MONTH)) {
setValue((ourYearMonthNoDashesFormat).parse(theValue));
setPrecision(MONTH);
clearTimeZone();
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
}
} 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));
setPrecision(MONTH);
clearTimeZone();
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support MONTH precision): " + theValue);
}
} 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(DAY);
clearTimeZone();
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
}
} 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));
setPrecision(DAY);
clearTimeZone();
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
}
} else if (theValue.length() >= 18) { //date and time with possible time zone
int dotIndex = theValue.indexOf('.', 18);
boolean hasMillis = dotIndex > -1;
if (!hasMillis && !isPrecisionAllowed(SECOND)) {
throw new DataFormatException("Invalid date/time string (data type does not support SECONDS precision): " + theValue);
} else if (hasMillis && !isPrecisionAllowed(MILLI)) {
throw new DataFormatException("Invalid date/time string (data type " + getClass().getSimpleName() + " does not support MILLIS precision):" + theValue);
}
if(hasMillis){
try {
myValue = ourYearMonthDayTimeMilliZoneFormat.parse(theValue);
} catch (ParseException p){
try{
if(theValue.endsWith("Z"))
myValue = ourYearMonthDayTimeMilliUTCZFormat.parse(theValue);
else
myValue = ourYearMonthDayTimeMilliFormat.parse(theValue);
}catch(ParseException p2){
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
}
}
setTimeZone(theValue, hasMillis);
setPrecision(TemporalPrecisionEnum.MILLI);
}else{
try{
myValue = ourYearMonthDayTimeZoneFormat.parse(theValue);
}catch(ParseException p){
try{
if(theValue.endsWith("Z"))
myValue = ourYearMonthDayTimeUTCZFormat.parse(theValue);
else
myValue = ourYearMonthDayTimeFormat.parse(theValue);
}catch(ParseException p2){
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
}
}
setTimeZone(theValue, hasMillis);
setPrecision(TemporalPrecisionEnum.SECOND);
}
} else {
throw new DataFormatException("Invalid date/time string (invalid length): " + theValue);
}
} catch (ParseException e) {
throw new DataFormatException("Invalid date string (" + e.getMessage() + "): " + theValue);
}
}
private void setTimeZone(String theValueString, boolean hasMillis) {
clearTimeZone();
int timeZoneStart = 19;
if(hasMillis) timeZoneStart += 4;
if (hasMillis)
timeZoneStart += 4;
if (theValueString.endsWith("Z")) {
setTimeZoneZulu(true);
} else if (theValueString.indexOf("GMT", timeZoneStart) != -1) {
setTimeZone(TimeZone.getTimeZone(theValueString.substring(timeZoneStart)));
} else if (theValueString.indexOf('+', timeZoneStart) != -1 || theValueString.indexOf('-', timeZoneStart) != -1) {
setTimeZone(TimeZone.getTimeZone("GMT"+theValueString.substring(timeZoneStart)));
setTimeZone(TimeZone.getTimeZone("GMT" + theValueString.substring(timeZoneStart)));
}
}
private void clearTimeZone() {
myTimeZone = null;
myTimeZoneZulu = false;
public void setTimeZone(TimeZone theTimeZone) {
myTimeZone = theTimeZone;
updateStringValue();
}
public void setTimeZoneZulu(boolean theTimeZoneZulu) {
myTimeZoneZulu = theTimeZoneZulu;
updateStringValue();
}
@Override
public void setValue(Date theValue) {
clearTimeZone();
super.setValue(theValue);
}
/**
* To be implemented by subclasses to indicate whether the given precision is allowed by this type
* Sets the value of this date/time using the specified level of precision
*
* @param theValue The date value
* @param thePrecision The precision
* @throws DataFormatException
*/
abstract boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision);
public void setValue(Date theValue, TemporalPrecisionEnum thePrecision) throws DataFormatException {
clearTimeZone();
super.setValue(theValue);
myPrecision = thePrecision;
}
@Override
public void setValueAsString(String theValue) throws DataFormatException {
clearTimeZone();
super.setValueAsString(theValue);
}
}

View File

@ -0,0 +1,409 @@
package ca.uhn.fhir.model.primitive;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import static ca.uhn.fhir.model.api.TemporalPrecisionEnum.*;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;
import java.util.regex.Pattern;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import ca.uhn.fhir.model.api.BasePrimitive;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.parser.DataFormatException;
public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
/*
* Add any new formatters to the static block below!!
*/
private static final List<FastDateFormat> ourFormatters;
private static final Pattern ourYearDashMonthDashDayPattern = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}");
private static final Pattern ourYearDashMonthPattern = Pattern.compile("[0-9]{4}-[0-9]{2}");
private static final FastDateFormat ourYearFormat = FastDateFormat.getInstance("yyyy");
private static final FastDateFormat ourYearMonthDayFormat = FastDateFormat.getInstance("yyyy-MM-dd");
private static final FastDateFormat ourYearMonthDayNoDashesFormat = FastDateFormat.getInstance("yyyyMMdd");
private static final Pattern ourYearMonthDayPattern = Pattern.compile("[0-9]{4}[0-9]{2}[0-9]{2}");
private static final FastDateFormat ourYearMonthDayTimeFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss");
private static final FastDateFormat ourYearMonthDayTimeMilliFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS");
private static final FastDateFormat ourYearMonthDayTimeMilliUTCZFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone("UTC"));
private static final FastDateFormat ourYearMonthDayTimeMilliZoneFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZZ");
private static final FastDateFormat ourYearMonthDayTimeUTCZFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("UTC"));
private static final FastDateFormat ourYearMonthDayTimeZoneFormat = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ssZZ");
private static final FastDateFormat ourYearMonthFormat = FastDateFormat.getInstance("yyyy-MM");
private static final FastDateFormat ourYearMonthNoDashesFormat = FastDateFormat.getInstance("yyyyMM");
private static final Pattern ourYearMonthPattern = Pattern.compile("[0-9]{4}[0-9]{2}");
private static final Pattern ourYearPattern = Pattern.compile("[0-9]{4}");
static {
ArrayList<FastDateFormat> formatters = new ArrayList<FastDateFormat>();
formatters.add(ourYearFormat);
formatters.add(ourYearMonthDayFormat);
formatters.add(ourYearMonthDayNoDashesFormat);
formatters.add(ourYearMonthDayTimeFormat);
formatters.add(ourYearMonthDayTimeMilliFormat);
formatters.add(ourYearMonthDayTimeUTCZFormat);
formatters.add(ourYearMonthDayTimeMilliUTCZFormat);
formatters.add(ourYearMonthDayTimeMilliZoneFormat);
formatters.add(ourYearMonthDayTimeZoneFormat);
formatters.add(ourYearMonthFormat);
formatters.add(ourYearMonthNoDashesFormat);
ourFormatters = Collections.unmodifiableList(formatters);
}
private TemporalPrecisionEnum myPrecision = TemporalPrecisionEnum.SECOND;
private TimeZone myTimeZone;
private boolean myTimeZoneZulu = false;
private void clearTimeZone() {
myTimeZone = null;
myTimeZoneZulu = false;
<<<<<<< HEAD
}
/**
* Gets the precision for this datatype using field values from {@link Calendar}, such as {@link Calendar#MONTH}. Default is {@link Calendar#DAY_OF_MONTH}
*
* @see #setPrecision(int)
*/
public TemporalPrecisionEnum getPrecision() {
return myPrecision;
}
public TimeZone getTimeZone() {
return myTimeZone;
}
@Override
public Date getValue() {
return myValue;
=======
>>>>>>> issue50
}
@Override
protected String encode(Date theValue) {
if (theValue == null) {
return null;
} else {
switch (myPrecision) {
case DAY:
return ourYearMonthDayFormat.format(theValue);
case MONTH:
return ourYearMonthFormat.format(theValue);
case YEAR:
return ourYearFormat.format(theValue);
case SECOND:
if (myTimeZoneZulu) {
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
cal.setTime(theValue);
return ourYearMonthDayTimeFormat.format(cal) + "Z";
} else if (myTimeZone != null) {
GregorianCalendar cal = new GregorianCalendar(myTimeZone);
cal.setTime(theValue);
return ourYearMonthDayTimeZoneFormat.format(cal);
} else {
return ourYearMonthDayTimeFormat.format(theValue);
}
case MILLI:
if (myTimeZoneZulu) {
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
cal.setTime(theValue);
return ourYearMonthDayTimeMilliFormat.format(cal) + "Z";
} else if (myTimeZone != null) {
GregorianCalendar cal = new GregorianCalendar(myTimeZone);
cal.setTime(theValue);
return ourYearMonthDayTimeMilliZoneFormat.format(cal);
} else {
return ourYearMonthDayTimeMilliFormat.format(theValue);
}
}
throw new IllegalStateException("Invalid precision (this is a HAPI bug, shouldn't happen): " + myPrecision);
}
}
/**
<<<<<<< HEAD
=======
* Gets the precision for this datatype using field values from {@link Calendar}, such as {@link Calendar#MONTH}. Default is {@link Calendar#DAY_OF_MONTH}
*
* @see #setPrecision(int)
*/
public TemporalPrecisionEnum getPrecision() {
return myPrecision;
}
public TimeZone getTimeZone() {
return myTimeZone;
}
/**
>>>>>>> issue50
* To be implemented by subclasses to indicate whether the given precision is allowed by this type
*/
abstract boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision);
public boolean isTimeZoneZulu() {
return myTimeZoneZulu;
}
/**
* Returns <code>true</code> if this object represents a date that is today's date
*
* @throws NullPointerException
* if {@link #getValue()} returns <code>null</code>
*/
public boolean isToday() {
<<<<<<< HEAD
Validate.notNull(myValue, getClass().getSimpleName() + " contains null value");
return DateUtils.isSameDay(new Date(), myValue);
}
/**
* Sets the precision for this datatype using field values from {@link Calendar}. Valid values are:
* <ul>
* <li>{@link Calendar#SECOND}
* <li>{@link Calendar#DAY_OF_MONTH}
* <li>{@link Calendar#MONTH}
* <li>{@link Calendar#YEAR}
* </ul>
*
* @throws DataFormatException
*/
public void setPrecision(TemporalPrecisionEnum thePrecision) throws DataFormatException {
if (thePrecision == null) {
throw new NullPointerException("Precision may not be null");
}
myPrecision = thePrecision;
}
private void setTimeZone(String theValueString, boolean hasMillis) {
clearTimeZone();
int timeZoneStart = 19;
if (hasMillis)
timeZoneStart += 4;
if (theValueString.endsWith("Z")) {
setTimeZoneZulu(true);
} else if (theValueString.indexOf("GMT", timeZoneStart) != -1) {
setTimeZone(TimeZone.getTimeZone(theValueString.substring(timeZoneStart)));
} else if (theValueString.indexOf('+', timeZoneStart) != -1 || theValueString.indexOf('-', timeZoneStart) != -1) {
setTimeZone(TimeZone.getTimeZone("GMT" + theValueString.substring(timeZoneStart)));
}
}
public void setTimeZone(TimeZone theTimeZone) {
myTimeZone = theTimeZone;
}
public void setTimeZoneZulu(boolean theTimeZoneZulu) {
myTimeZoneZulu = theTimeZoneZulu;
=======
Validate.notNull(getValue(), getClass().getSimpleName() + " contains null value");
return DateUtils.isSameDay(new Date(), getValue());
>>>>>>> issue50
}
@Override
protected Date parse(String theValue) throws DataFormatException {
try {
if (theValue.length() == 4 && ourYearPattern.matcher(theValue).matches()) {
if (isPrecisionAllowed(YEAR)) {
setPrecision(YEAR);
clearTimeZone();
return ((ourYearFormat).parse(theValue));
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support YEAR precision): " + theValue);
}
} else if (theValue.length() == 6 && ourYearMonthPattern.matcher(theValue).matches()) {
// Eg. 198401 (allow this just to be lenient)
if (isPrecisionAllowed(MONTH)) {
setPrecision(MONTH);
clearTimeZone();
return ((ourYearMonthNoDashesFormat).parse(theValue));
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
}
} else if (theValue.length() == 7 && ourYearDashMonthPattern.matcher(theValue).matches()) {
// E.g. 1984-01 (this is valid according to the spec)
if (isPrecisionAllowed(MONTH)) {
setPrecision(MONTH);
clearTimeZone();
return ((ourYearMonthFormat).parse(theValue));
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support MONTH precision): " + theValue);
}
} else if (theValue.length() == 8 && ourYearMonthDayPattern.matcher(theValue).matches()) {
// Eg. 19840101 (allow this just to be lenient)
if (isPrecisionAllowed(DAY)) {
setPrecision(DAY);
clearTimeZone();
return ((ourYearMonthDayNoDashesFormat).parse(theValue));
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
}
} else if (theValue.length() == 10 && ourYearDashMonthDashDayPattern.matcher(theValue).matches()) {
// E.g. 1984-01-01 (this is valid according to the spec)
if (isPrecisionAllowed(DAY)) {
setPrecision(DAY);
clearTimeZone();
return ((ourYearMonthDayFormat).parse(theValue));
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
}
} else if (theValue.length() >= 18) { // date and time with possible time zone
int dotIndex = theValue.indexOf('.', 18);
boolean hasMillis = dotIndex > -1;
if (!hasMillis && !isPrecisionAllowed(SECOND)) {
throw new DataFormatException("Invalid date/time string (data type does not support SECONDS precision): " + theValue);
} else if (hasMillis && !isPrecisionAllowed(MILLI)) {
throw new DataFormatException("Invalid date/time string (data type " + getClass().getSimpleName() + " does not support MILLIS precision):" + theValue);
}
<<<<<<< HEAD
if (hasMillis) {
try {
if (hasOffset(theValue)) {
myValue = ourYearMonthDayTimeMilliZoneFormat.parse(theValue);
} else if (theValue.endsWith("Z"))
myValue = ourYearMonthDayTimeMilliUTCZFormat.parse(theValue);
else
myValue = ourYearMonthDayTimeMilliFormat.parse(theValue);
} catch (ParseException p2) {
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
}
setTimeZone(theValue, hasMillis);
=======
Calendar cal;
try {
cal = DatatypeConverter.parseDateTime(theValue);
} catch (IllegalArgumentException e) {
throw new DataFormatException("Invalid data/time string (" + e.getMessage() + "): " + theValue);
}
if (dotIndex == -1) {
setPrecision(TemporalPrecisionEnum.SECOND);
} else {
>>>>>>> issue50
setPrecision(TemporalPrecisionEnum.MILLI);
} else {
try {
if (hasOffset(theValue)) {
myValue = ourYearMonthDayTimeZoneFormat.parse(theValue);
} else if (theValue.endsWith("Z")) {
myValue = ourYearMonthDayTimeUTCZFormat.parse(theValue);
} else {
myValue = ourYearMonthDayTimeFormat.parse(theValue);
}
} catch (ParseException p2) {
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
}
setTimeZone(theValue, hasMillis);
setPrecision(TemporalPrecisionEnum.SECOND);
}
<<<<<<< HEAD
=======
return cal.getTime();
>>>>>>> issue50
} else {
throw new DataFormatException("Invalid date/time string (invalid length): " + theValue);
}
} catch (ParseException e) {
throw new DataFormatException("Invalid date string (" + e.getMessage() + "): " + theValue);
}
}
<<<<<<< HEAD
private boolean hasOffset(String theValue) {
boolean inTime = false;
for (int i = 0; i < theValue.length(); i++) {
switch (theValue.charAt(i)) {
case 'T':
inTime = true;
break;
case '+':
case '-':
if (inTime) {
return true;
}
break;
}
}
return false;
}
/**
* For unit tests only
*/
static List<FastDateFormat> getFormatters() {
return ourFormatters;
=======
/**
* Sets the precision for this datatype using field values from {@link Calendar}. Valid values are:
* <ul>
* <li>{@link Calendar#SECOND}
* <li>{@link Calendar#DAY_OF_MONTH}
* <li>{@link Calendar#MONTH}
* <li>{@link Calendar#YEAR}
* </ul>
*
* @throws DataFormatException
*/
public void setPrecision(TemporalPrecisionEnum thePrecision) throws DataFormatException {
if (thePrecision == null) {
throw new NullPointerException("Precision may not be null");
}
myPrecision = thePrecision;
}
public void setTimeZone(TimeZone theTimeZone) {
myTimeZone = theTimeZone;
}
public void setTimeZoneZulu(boolean theTimeZoneZulu) {
myTimeZoneZulu = theTimeZoneZulu;
}
@Override
public void setValue(Date theValue) throws DataFormatException {
clearTimeZone();
super.setValue(theValue);
}
@Override
public void setValueAsString(String theValue) throws DataFormatException {
clearTimeZone();
super.setValueAsString(theValue);
>>>>>>> issue50
}
}

View File

@ -28,8 +28,6 @@ import ca.uhn.fhir.parser.DataFormatException;
@DatatypeDef(name = "boolean")
public class BooleanDt extends BasePrimitive<Boolean> {
private Boolean myValue;
/**
* Constructor
*/
@ -41,45 +39,28 @@ public class BooleanDt extends BasePrimitive<Boolean> {
* Constructor
*/
@SimpleSetter
public BooleanDt(@SimpleSetter.Parameter(name="theBoolean") boolean theBoolean) {
public BooleanDt(@SimpleSetter.Parameter(name = "theBoolean") boolean theBoolean) {
setValue(theBoolean);
}
@Override
public void setValueAsString(String theValue) throws DataFormatException {
protected Boolean parse(String theValue) {
if ("true".equals(theValue)) {
myValue = Boolean.TRUE;
return Boolean.TRUE;
} else if ("false".equals(theValue)) {
myValue = Boolean.FALSE;
return Boolean.FALSE;
} else {
throw new DataFormatException("Invalid boolean string: '" + theValue + "'");
}
}
@Override
public String getValueAsString() {
if (myValue == null) {
return null;
} else if (Boolean.TRUE.equals(myValue)) {
protected String encode(Boolean theValue) {
if (Boolean.TRUE.equals(theValue)) {
return "true";
} else {
return "false";
}
}
@Override
public void setValue(Boolean theValue) {
myValue = theValue;
}
@Override
public Boolean getValue() {
return myValue;
}
}

View File

@ -29,8 +29,6 @@ import ca.uhn.fhir.parser.DataFormatException;
@DatatypeDef(name = "code")
public class CodeDt extends BasePrimitive<String> implements ICodedDatatype, Comparable<CodeDt> {
private String myValue;
/**
* Constructor
*/
@ -46,31 +44,6 @@ public class CodeDt extends BasePrimitive<String> implements ICodedDatatype, Com
setValue(theCode);
}
@Override
public String getValue() {
return myValue;
}
@Override
public void setValue(String theValue) throws DataFormatException {
if (theValue == null) {
myValue = null;
} else {
String newValue = theValue.trim();
myValue = newValue;
}
}
@Override
public void setValueAsString(String theValue) throws DataFormatException {
setValue(theValue);
}
@Override
public String getValueAsString() {
return getValue();
}
@Override
public int compareTo(CodeDt theCode) {
if (theCode == null) {
@ -79,4 +52,14 @@ public class CodeDt extends BasePrimitive<String> implements ICodedDatatype, Com
return defaultString(getValue()).compareTo(defaultString(theCode.getValue()));
}
@Override
protected String parse(String theValue) {
return theValue.trim();
}
@Override
protected String encode(String theValue) {
return theValue;
}
}

View File

@ -47,6 +47,7 @@ public class DateDt extends BaseDateTimeDt {
@SimpleSetter(suffix="WithDayPrecision")
public DateDt(@SimpleSetter.Parameter(name = "theDate") Date theDate) {
setValue(theDate);
setPrecision(DEFAULT_PRECISION);
}
/**
@ -75,4 +76,14 @@ public class DateDt extends BaseDateTimeDt {
}
}
/**
* Returns the default precision for this datatype
*
* @see #DEFAULT_PRECISION
*/
@Override
protected TemporalPrecisionEnum getDefaultPrecisionForDatatype() {
return DEFAULT_PRECISION;
}
}

View File

@ -98,4 +98,15 @@ public class DateTimeDt extends BaseDateTimeDt {
return new DateTimeDt(new Date(), TemporalPrecisionEnum.SECOND);
}
/**
* Returns the default precision for this datatype
*
* @see #DEFAULT_PRECISION
*/
@Override
protected TemporalPrecisionEnum getDefaultPrecisionForDatatype() {
return DEFAULT_PRECISION;
}
}

View File

@ -27,13 +27,10 @@ import java.math.RoundingMode;
import ca.uhn.fhir.model.api.BasePrimitive;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.SimpleSetter;
import ca.uhn.fhir.parser.DataFormatException;
@DatatypeDef(name = "decimal")
public class DecimalDt extends BasePrimitive<BigDecimal> implements Comparable<DecimalDt> {
private BigDecimal myValue;
/**
* Constructor
*/
@ -41,10 +38,6 @@ public class DecimalDt extends BasePrimitive<BigDecimal> implements Comparable<D
super();
}
public Number getValueAsNumber() {
return myValue;
}
/**
* Constructor
*/
@ -63,31 +56,6 @@ public class DecimalDt extends BasePrimitive<BigDecimal> implements Comparable<D
setValue(BigDecimal.valueOf(theValue));
}
/**
* Rounds the value to the given prevision
*
* @see MathContext#getPrecision()
*/
public void round(int thePrecision) {
if (getValue()!=null) {
BigDecimal newValue = getValue().round(new MathContext(thePrecision));
setValue(newValue);
}
}
/**
* Rounds the value to the given prevision
*
* @see MathContext#getPrecision()
* @see MathContext#getRoundingMode()
*/
public void round(int thePrecision, RoundingMode theRoundingMode) {
if (getValue()!=null) {
BigDecimal newValue = getValue().round(new MathContext(thePrecision, theRoundingMode));
setValue(newValue);
}
}
/**
* Constructor
*/
@ -104,58 +72,70 @@ public class DecimalDt extends BasePrimitive<BigDecimal> implements Comparable<D
}
@Override
public void setValueAsString(String theValue) throws DataFormatException {
if (theValue == null) {
myValue = null;
} else {
myValue = new BigDecimal(theValue);
public int compareTo(DecimalDt theObj) {
if (getValue() == null && theObj.getValue() == null) {
return 0;
}
}
@Override
public String getValueAsString() {
if (myValue == null) {
return null;
if (getValue() != null && theObj.getValue() == null) {
return 1;
}
return myValue.toPlainString();
if (getValue() == null && theObj.getValue() != null) {
return -1;
}
return getValue().compareTo(theObj.getValue());
}
@Override
public BigDecimal getValue() {
return myValue;
}
@Override
public void setValue(BigDecimal theValue) throws DataFormatException {
myValue = theValue;
}
/**
* Sets a new value using an integer
*/
public void setValueAsInteger(int theValue) {
myValue = new BigDecimal(theValue);
protected String encode(BigDecimal theValue) {
return getValue().toPlainString();
}
/**
* Gets the value as an integer, using {@link BigDecimal#intValue()}
*/
public int getValueAsInteger() {
return myValue.intValue();
return getValue().intValue();
}
public Number getValueAsNumber() {
return getValue();
}
@Override
public int compareTo(DecimalDt theObj) {
if (myValue == null && theObj.getValue() == null) {
return 0;
protected BigDecimal parse(String theValue) {
return new BigDecimal(theValue);
}
/**
* Rounds the value to the given prevision
*
* @see MathContext#getPrecision()
*/
public void round(int thePrecision) {
if (getValue() != null) {
BigDecimal newValue = getValue().round(new MathContext(thePrecision));
setValue(newValue);
}
if (myValue != null && theObj.getValue() == null) {
return 1;
}
/**
* Rounds the value to the given prevision
*
* @see MathContext#getPrecision()
* @see MathContext#getRoundingMode()
*/
public void round(int thePrecision, RoundingMode theRoundingMode) {
if (getValue() != null) {
BigDecimal newValue = getValue().round(new MathContext(thePrecision, theRoundingMode));
setValue(newValue);
}
if (myValue == null && theObj.getValue() != null) {
return -1;
}
return myValue.compareTo(theObj.getValue());
}
/**
* Sets a new value using an integer
*/
public void setValueAsInteger(int theValue) {
setValue(new BigDecimal(theValue));
}
}

View File

@ -20,15 +20,17 @@ package ca.uhn.fhir.model.primitive;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.math.BigDecimal;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import ca.uhn.fhir.model.api.BasePrimitive;
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.SimpleSetter;
import ca.uhn.fhir.parser.DataFormatException;
@ -47,7 +49,7 @@ import ca.uhn.fhir.util.UrlUtil;
* </p>
*/
@DatatypeDef(name = "id")
public class IdDt extends BasePrimitive<String> {
public class IdDt implements IPrimitiveDatatype<String> {
private String myBaseUrl;
private boolean myHaveComponentParts;
@ -166,6 +168,24 @@ public class IdDt extends BasePrimitive<String> {
return ObjectUtils.equals(getResourceType(), theId.getResourceType()) && ObjectUtils.equals(getIdPart(), theId.getIdPart()) && ObjectUtils.equals(getVersionIdPart(), theId.getVersionIdPart());
}
@Override
public boolean equals(Object theArg0) {
if (!(theArg0 instanceof IdDt)) {
return false;
}
IdDt id = (IdDt)theArg0;
return StringUtils.equals(getValueAsString(), id.getValueAsString());
}
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder();
b.append(getValueAsString());
return b.toHashCode();
}
/**
* Returns the portion of this resource ID which corresponds to the server base URL. For example given the resource ID <code>http://example.com/fhir/Patient/123</code> the base URL would be
* <code>http://example.com/fhir</code>.
@ -312,7 +332,6 @@ public class IdDt extends BasePrimitive<String> {
/**
* Copies the value from the given IdDt to <code>this</code> IdDt. It is generally not neccesary to use this method but it is provided for consistency with the rest of the API.
*/
@Override
public void setId(IdDt theId) {
setValue(theId.getValue());
}
@ -481,4 +500,9 @@ public class IdDt extends BasePrimitive<String> {
return theIdPart.toPlainString();
}
@Override
public boolean isEmpty() {
return isBlank(getValue());
}
}

View File

@ -20,43 +20,19 @@ package ca.uhn.fhir.model.primitive;
* #L%
*/
import ca.uhn.fhir.model.api.BasePrimitive;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.parser.DataFormatException;
@DatatypeDef(name = "idref")
public class IdrefDt extends BasePrimitive<String> {
public class IdrefDt extends StringDt {
private IElement myTarget;
private String myValue;
public IElement getTarget() {
return myTarget;
}
@Override
public String getValue() {
return myValue;
}
@Override
public String getValueAsString() throws DataFormatException {
return myValue;
}
public void setTarget(IElement theTarget) {
myTarget = theTarget;
}
@Override
public void setValue(String theValue) throws DataFormatException {
myValue = theValue;
}
@Override
public void setValueAsString(String theValue) throws DataFormatException {
myValue = theValue;
}
}

View File

@ -160,4 +160,15 @@ public class InstantDt extends BaseDateTimeDt {
return new InstantDt(new Date(), TemporalPrecisionEnum.MILLI);
}
/**
* Returns the default precision for this datatype
*
* @see #DEFAULT_PRECISION
*/
@Override
protected TemporalPrecisionEnum getDefaultPrecisionForDatatype() {
return DEFAULT_PRECISION;
}
}

View File

@ -28,8 +28,6 @@ import ca.uhn.fhir.parser.DataFormatException;
@DatatypeDef(name = "integer")
public class IntegerDt extends BasePrimitive<Integer> {
private Integer myValue;
/**
* Constructor
*/
@ -48,46 +46,27 @@ public class IntegerDt extends BasePrimitive<Integer> {
/**
* Constructor
*
* @param theIntegerAsString A string representation of an integer
* @throws DataFormatException If the string is not a valid integer representation
* @param theIntegerAsString
* A string representation of an integer
* @throws DataFormatException
* If the string is not a valid integer representation
*/
public IntegerDt(String theIntegerAsString) {
setValueAsString(theIntegerAsString);
}
public IntegerDt(Long theCount) {
// TODO Auto-generated constructor stub
}
@Override
public Integer getValue() {
return myValue;
}
@Override
public void setValue(Integer theValue) {
myValue = theValue;
}
@Override
public void setValueAsString(String theValue) throws DataFormatException {
if (theValue == null) {
myValue = null;
} else {
try {
myValue = Integer.parseInt(theValue);
} catch (NumberFormatException e) {
throw new DataFormatException(e);
}
protected Integer parse(String theValue) {
try {
return Integer.parseInt(theValue);
} catch (NumberFormatException e) {
throw new DataFormatException(e);
}
}
@Override
public String getValueAsString() {
if (myValue == null) {
return null;
}
return Integer.toString(myValue);
protected String encode(Integer theValue) {
return Integer.toString(theValue);
}
}

View File

@ -26,13 +26,10 @@ import ca.uhn.fhir.model.api.BasePrimitive;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.SimpleSetter;
import ca.uhn.fhir.parser.DataFormatException;
@DatatypeDef(name = "string")
public class StringDt extends BasePrimitive<String> implements IQueryParameterType {
private String myValue;
/**
* Create a new String
*/
@ -45,31 +42,11 @@ public class StringDt extends BasePrimitive<String> implements IQueryParameterTy
*/
@SimpleSetter
public StringDt(@SimpleSetter.Parameter(name = "theString") String theValue) {
myValue = theValue;
}
@Override
public String getValue() {
return myValue;
setValue(theValue);
}
public String getValueNotNull() {
return StringUtils.defaultString(myValue);
}
@Override
public String getValueAsString() {
return myValue;
}
@Override
public void setValue(String theValue) throws DataFormatException {
myValue = theValue;
}
@Override
public void setValueAsString(String theValue) throws DataFormatException {
myValue = theValue;
return StringUtils.defaultString(getValue());
}
/**
@ -77,14 +54,14 @@ public class StringDt extends BasePrimitive<String> implements IQueryParameterTy
*/
@Override
public String toString() {
return myValue;
return getValue();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((myValue == null) ? 0 : myValue.hashCode());
result = prime * result + ((getValue() == null) ? 0 : getValue().hashCode());
return result;
}
@ -97,10 +74,10 @@ public class StringDt extends BasePrimitive<String> implements IQueryParameterTy
if (getClass() != obj.getClass())
return false;
StringDt other = (StringDt) obj;
if (myValue == null) {
if (other.myValue != null)
if (getValue() == null) {
if (other.getValue() != null)
return false;
} else if (!myValue.equals(other.myValue))
} else if (!getValue().equals(other.getValue()))
return false;
return true;
}
@ -135,4 +112,14 @@ public class StringDt extends BasePrimitive<String> implements IQueryParameterTy
return null;
}
@Override
protected String parse(String theValue) {
return theValue;
}
@Override
protected String encode(String theValue) {
return theValue;
}
}

View File

@ -20,28 +20,25 @@ package ca.uhn.fhir.model.primitive;
* #L%
*/
import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.model.api.BasePrimitive;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.SimpleSetter;
import ca.uhn.fhir.parser.DataFormatException;
/**
* Represents a Time datatype, per the FHIR specification. A time is a specification of hours and minutes (and optionally
* milliseconds), with NO date and NO timezone information attached. It is expressed as a string in the form
* <code>HH:mm:ss[.SSSS]</code>
* Represents a Time datatype, per the FHIR specification. A time is a specification of hours and minutes (and optionally milliseconds), with NO date and NO timezone information attached. It is
* expressed as a string in the form <code>HH:mm:ss[.SSSS]</code>
*
* <p>
* This datatype is not valid in FHIR DSTU1
* </p>
*
* @since FHIR DSTU 2 / HAPI 0.8
*
* TODO: have a way of preventing this from being used in DSTU1 resources
* TODO: validate time?
*/
@DatatypeDef(name = "time")
public class TimeDt extends BasePrimitive<String> implements IQueryParameterType {
private String myValue;
public class TimeDt extends StringDt implements IQueryParameterType {
/**
* Create a new String
@ -55,94 +52,7 @@ public class TimeDt extends BasePrimitive<String> implements IQueryParameterType
*/
@SimpleSetter
public TimeDt(@SimpleSetter.Parameter(name = "theString") String theValue) {
myValue = theValue;
}
@Override
public String getValue() {
return myValue;
}
public String getValueNotNull() {
return StringUtils.defaultString(myValue);
}
@Override
public String getValueAsString() {
return myValue;
}
@Override
public void setValue(String theValue) throws DataFormatException {
myValue = theValue;
}
@Override
public void setValueAsString(String theValue) throws DataFormatException {
myValue = theValue;
}
/**
* Returns the value of this string, or <code>null</code>
*/
@Override
public String toString() {
return myValue;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((myValue == null) ? 0 : myValue.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TimeDt other = (TimeDt) obj;
if (myValue == null) {
if (other.myValue != null)
return false;
} else if (!myValue.equals(other.myValue))
return false;
return true;
}
/**
* {@inheritDoc}
*/
@Override
public void setValueAsQueryToken(String theQualifier, String theValue) {
setValue(theValue);
}
/**
* {@inheritDoc}
*/
@Override
public String getValueAsQueryToken() {
return getValue();
}
/**
* Returns <code>true</code> if this datatype has no extensions, and has either a <code>null</code> value or an empty ("") value.
*/
@Override
public boolean isEmpty() {
boolean retVal = super.isBaseEmpty() && StringUtils.isBlank(getValue());
return retVal;
}
@Override
public String getQueryParameterQualifier() {
return null;
}
}

View File

@ -33,7 +33,23 @@ import ca.uhn.fhir.parser.DataFormatException;
@DatatypeDef(name = "uri")
public class UriDt extends BasePrimitive<URI> {
private URI myValue;
/**
* Creates a new UriDt instance which uses the given OID as the content (and prepends "urn:oid:" to the OID string
* in the value of the newly created UriDt, per the FHIR specification).
*
* @param theOid
* The OID to use (<code>null</code> is acceptable and will result in a UriDt instance with a
* <code>null</code> value)
* @return A new UriDt instance
*/
public static UriDt fromOid(String theOid) {
if (theOid == null) {
return new UriDt();
}
return new UriDt("urn:oid:" + theOid);
}
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UriDt.class);
/**
* Create a new String
@ -51,57 +67,8 @@ public class UriDt extends BasePrimitive<URI> {
}
@Override
public URI getValue() {
return myValue;
}
@Override
public void setValue(URI theValue) {
myValue = theValue;
}
@Override
public void setValueAsString(String theValue) throws DataFormatException {
if (theValue == null) {
myValue = null;
} else {
try {
myValue = new URI(theValue);
} catch (URISyntaxException e) {
throw new DataFormatException("Unable to parse URI value", e);
}
}
}
@Override
public String getValueAsString() {
if (myValue == null) {
return null;
} else {
return myValue.toASCIIString();
}
}
@Override
public String toString() {
return getValueAsString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((myValue == null) ? 0 : myValue.hashCode());
return result;
}
/**
* Compares the given string to the string representation of this URI. In many cases it is preferable to use this
* instead of the standard {@link #equals(Object)} method, since that method returns <code>false</code> unless it is
* passed an instance of {@link UriDt}
*/
public boolean equals(String theString) {
return StringUtils.equals(getValueAsString(), theString);
protected String encode(URI theValue) {
return getValue().toASCIIString();
}
@Override
@ -114,21 +81,42 @@ public class UriDt extends BasePrimitive<URI> {
return false;
UriDt other = (UriDt) obj;
if (myValue == null && other.myValue == null) {
if (getValue() == null && other.getValue() == null) {
return true;
}
if (myValue == null || other.myValue == null) {
if (getValue() == null || other.getValue() == null) {
return false;
}
URI normalize = normalize(myValue);
URI normalize2 = normalize(other.myValue);
URI normalize = normalize(getValue());
URI normalize2 = normalize(other.getValue());
return normalize.equals(normalize2);
}
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UriDt.class);
/**
* Compares the given string to the string representation of this URI. In many cases it is preferable to use this
* instead of the standard {@link #equals(Object)} method, since that method returns <code>false</code> unless it is
* passed an instance of {@link UriDt}
*/
public boolean equals(String theString) {
return StringUtils.equals(getValueAsString(), theString);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
URI normalize = normalize(getValue());
result = prime * result + ((normalize == null) ? 0 : normalize.hashCode());
return result;
}
private URI normalize(URI theValue) {
if (theValue == null) {
return null;
}
URI retVal = (theValue.normalize());
String urlString = retVal.toString();
if (urlString.endsWith("/") && urlString.length() > 1) {
@ -141,20 +129,13 @@ public class UriDt extends BasePrimitive<URI> {
return retVal;
}
/**
* Creates a new UriDt instance which uses the given OID as the content (and prepends "urn:oid:" to the OID string
* in the value of the newly created UriDt, per the FHIR specification).
*
* @param theOid
* The OID to use (<code>null</code> is acceptable and will result in a UriDt instance with a
* <code>null</code> value)
* @return A new UriDt instance
*/
public static UriDt fromOid(String theOid) {
if (theOid == null) {
return new UriDt();
@Override
protected URI parse(String theValue) {
try {
return new URI(theValue);
} catch (URISyntaxException e) {
throw new DataFormatException("Unable to parse URI value", e);
}
return new UriDt("urn:oid:" + theOid);
}
}

View File

@ -41,8 +41,6 @@ import ca.uhn.fhir.util.XmlUtil;
@DatatypeDef(name = "xhtml")
public class XhtmlDt extends BasePrimitive<List<XMLEvent>> {
private List<XMLEvent> myValue;
/**
* Constructor
*/
@ -73,18 +71,27 @@ public class XhtmlDt extends BasePrimitive<List<XMLEvent>> {
*/
@Override
public void setValueAsString(String theValue) throws DataFormatException {
if (theValue == null) {
myValue = null;
return;
String value = theValue.trim();
if (value.charAt(0) != '<') {
value = "<div>" + value + "</div>";
}
super.setValueAsString(value);
}
public boolean hasContent() {
return getValue() != null && getValue().size() > 0;
}
@Override
protected List<XMLEvent> parse(String theValue) {
String val = theValue.trim();
if (!val.startsWith("<")) {
val = "<div>" + val + "</div>";
}
if (val.startsWith("<?") && val.endsWith("?>")) {
myValue = null;
return;
return null;
}
try {
@ -103,7 +110,7 @@ public class XhtmlDt extends BasePrimitive<List<XMLEvent>> {
value.add(next);
}
}
setValue(value);
return value;
} catch (XMLStreamException e) {
throw new DataFormatException("String does not appear to be valid XML/XHTML (error is \"" + e.getMessage() + "\"): " + theValue, e);
@ -113,14 +120,11 @@ public class XhtmlDt extends BasePrimitive<List<XMLEvent>> {
}
@Override
public String getValueAsString() throws DataFormatException {
if (myValue == null) {
return null;
}
protected String encode(List<XMLEvent> theValue) {
try {
StringWriter w = new StringWriter();
XMLEventWriter ew = XmlUtil.createXmlWriter(w);
for (XMLEvent next : myValue) {
for (XMLEvent next : getValue()) {
if (next.isCharacters()) {
ew.add(next);
} else {
@ -136,18 +140,4 @@ public class XhtmlDt extends BasePrimitive<List<XMLEvent>> {
}
}
@Override
public List<XMLEvent> getValue() {
return myValue;
}
@Override
public void setValue(List<XMLEvent> theValue) throws DataFormatException {
myValue = theValue;
}
public boolean hasContent() {
return myValue != null && myValue.size() > 0;
}
}

View File

@ -511,6 +511,10 @@ public class GenericClient extends BaseClient implements IGenericClient {
return resp;
}
protected EncodingEnum getParamEncoding() {
return myParamEncoding;
}
protected IResource parseResourceBody(String theResourceBody) {
EncodingEnum encoding = null;
for (int i = 0; i < theResourceBody.length() && encoding == null; i++) {
@ -571,6 +575,11 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
myId = getPreferredId(myResource, myId);
// If an explicit encoding is chosen, we will re-serialize to ensure the right encoding
if (getParamEncoding() != null) {
myResourceBody = null;
}
BaseHttpClientInvocation invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myId, myContext);
RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource);
@ -1102,6 +1111,11 @@ public class GenericClient extends BaseClient implements IGenericClient {
throw new InvalidRequestException("No ID supplied for resource to update, can not invoke server");
}
// If an explicit encoding is chosen, we will re-serialize to ensure the right encoding
if (getParamEncoding() != null) {
myResourceBody = null;
}
BaseHttpClientInvocation invocation = MethodUtil.createUpdateInvocation(myResource, myResourceBody, myId, myContext);
RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource);

View File

@ -31,4 +31,9 @@ public interface ITransaction {
ITransactionTyped<Bundle> withBundle(Bundle theResources);
// *****
// TODO: add withString version
// If we add a withString version, make sure to auto-detect content type!
// *****
}

View File

@ -44,6 +44,7 @@ import ca.uhn.fhir.model.dstu.resource.Binary;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -165,6 +166,10 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca
EncodingEnum encoding = null;
encoding = theEncoding;
if (myContents != null) {
encoding = MethodUtil.detectEncoding(myContents);
}
if (encoding == EncodingEnum.JSON) {
parser = myContext.newJsonParser();
} else {
@ -174,6 +179,7 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca
AbstractHttpEntity entity;
if (myParams != null) {
contentType= null;
List<NameValuePair> parameters = new ArrayList<NameValuePair>();
for (Entry<String, List<String>> nextParam : myParams.entrySet()) {
List<String> value = nextParam.getValue();
@ -215,7 +221,9 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca
HttpRequestBase retVal = createRequest(url, entity);
super.addHeadersToRequest(retVal);
// retVal.addHeader(Constants.HEADER_CONTENT_TYPE, con);
if (contentType != null) {
retVal.addHeader(Constants.HEADER_CONTENT_TYPE, contentType);
}
return retVal;
}

View File

@ -115,7 +115,8 @@ public class MethodUtil {
}
addTagsToPostOrPut(theResource, retVal);
// addContentTypeHeaderBasedOnDetectedType(retVal, theResourceBody);
return retVal;
}
@ -260,9 +261,23 @@ public class MethodUtil {
}
addTagsToPostOrPut(theResource, retVal);
// addContentTypeHeaderBasedOnDetectedType(retVal, theResourceBody);
return retVal;
}
public static EncodingEnum detectEncoding(String theBody) {
for (int i = 0; i < theBody.length(); i++) {
switch (theBody.charAt(i)) {
case '<':
return EncodingEnum.XML;
case '{':
return EncodingEnum.JSON;
}
}
return EncodingEnum.XML;
}
public static HttpGetClientInvocation createConformanceInvocation() {
return new HttpGetClientInvocation("metadata");
}

View File

@ -179,9 +179,16 @@ public class FhirTerser {
switch (theDefinition.getChildType()) {
case PRIMITIVE_XHTML:
case PRIMITIVE_DATATYPE:
case RESOURCE_REF:
// These are primitive types
break;
case RESOURCE_REF:
ResourceReferenceDt resRefDt = (ResourceReferenceDt)theElement;
if (resRefDt.getReference().getValue() == null && resRefDt.getResource() != null) {
IResource theResource = resRefDt.getResource();
BaseRuntimeElementCompositeDefinition<?> def = myContext.getResourceDefinition(theResource);
visit(theResource, null, def, theCallback);
}
break;
case RESOURCE_BLOCK:
case COMPOSITE_DATATYPE:
case RESOURCE: {

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.dao;
import static org.apache.commons.lang3.StringUtils.*;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Collection;
@ -79,6 +80,7 @@ import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum;
import ca.uhn.fhir.model.primitive.BaseDateTimeDt;
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.model.primitive.UriDt;
import ca.uhn.fhir.parser.IParser;
@ -306,21 +308,22 @@ public abstract class BaseFhirDao implements IDao {
ArrayList<ResourceLink> retVal = new ArrayList<ResourceLink>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource);
FhirTerser t = getContext().newTerser();
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) {
if (nextSpDef.getParamType() != SearchParamTypeEnum.REFERENCE) {
continue;
}
String nextPath = nextSpDef.getPath();
String nextPathsUnsplit = nextSpDef.getPath();
if (isBlank(nextPathsUnsplit)) {
continue;
}
boolean multiType = false;
if (nextPath.endsWith("[x]")) {
if (nextPathsUnsplit.endsWith("[x]")) {
multiType = true;
}
List<Object> values = t.getValues(theResource, nextPath);
for (Object nextObject : values) {
for (Object nextObject : extractValues(nextPathsUnsplit, theResource)) {
if (nextObject == null) {
continue;
}
@ -338,7 +341,7 @@ public abstract class BaseFhirDao implements IDao {
String typeString = nextValue.getReference().getResourceType();
if (isBlank(typeString)) {
throw new InvalidRequestException("Invalid resource reference found at path[" + nextPath + "] - Does not contain resource type - " + nextValue.getReference().getValue());
throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Does not contain resource type - " + nextValue.getReference().getValue());
}
Class<? extends IResource> type = getContext().getResourceDefinition(typeString).getImplementingClass();
String id = nextValue.getReference().getIdPart();
@ -355,14 +358,14 @@ public abstract class BaseFhirDao implements IDao {
valueOf = translateForcedIdToPid(nextValue.getReference());
} catch (Exception e) {
String resName = getContext().getResourceDefinition(type).getName();
throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPath + " (this is an invalid ID, must be numeric on this server)");
throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit + " (this is an invalid ID, must be numeric on this server)");
}
ResourceTable target = myEntityManager.find(ResourceTable.class, valueOf);
if (target == null) {
String resName = getContext().getResourceDefinition(type).getName();
throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPath);
throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
}
nextEntity = new ResourceLink(nextPath, theEntity, target);
nextEntity = new ResourceLink(nextPathsUnsplit, theEntity, target);
} else {
if (!multiType) {
throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());
@ -381,25 +384,42 @@ public abstract class BaseFhirDao implements IDao {
return retVal;
}
private List<Object> extractValues(String thePaths, IResource theResource) {
List<Object> values = new ArrayList<Object>();
String[] nextPathsSplit = thePaths.split("\\|");
FhirTerser t = getContext().newTerser();
for (String nextPath : nextPathsSplit) {
String nextPathTrimmed = nextPath.trim();
try {
values.addAll(t.getValues(theResource, nextPathTrimmed));
} catch (Exception e) {
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
ourLog.warn("Failed to index values from path[{}] in resource type[{}]: ", nextPathTrimmed, def.getName(), e.toString());
}
}
return values;
}
protected List<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IResource theResource) {
ArrayList<ResourceIndexedSearchParamDate> retVal = new ArrayList<ResourceIndexedSearchParamDate>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource);
FhirTerser t = getContext().newTerser();
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) {
if (nextSpDef.getParamType() != SearchParamTypeEnum.DATE) {
continue;
}
String nextPath = nextSpDef.getPath();
if (isBlank(nextPath)) {
continue;
}
boolean multiType = false;
if (nextPath.endsWith("[x]")) {
multiType = true;
}
List<Object> values = t.getValues(theResource, nextPath);
for (Object nextObject : values) {
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null) {
continue;
}
@ -440,15 +460,17 @@ public abstract class BaseFhirDao implements IDao {
ArrayList<ResourceIndexedSearchParamNumber> retVal = new ArrayList<ResourceIndexedSearchParamNumber>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource);
FhirTerser t = getContext().newTerser();
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) {
if (nextSpDef.getParamType() != SearchParamTypeEnum.NUMBER) {
continue;
}
String nextPath = nextSpDef.getPath();
List<Object> values = t.getValues(theResource, nextPath);
for (Object nextObject : values) {
if (isBlank(nextPath)) {
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || ((IDatatype) nextObject).isEmpty()) {
continue;
}
@ -500,6 +522,15 @@ public abstract class BaseFhirDao implements IDao {
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, nextValue.getValue().getValue());
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else if (nextObject instanceof IntegerDt) {
IntegerDt nextValue = (IntegerDt) nextObject;
if (nextValue.getValue()==null) {
continue;
}
ResourceIndexedSearchParamNumber nextEntity = new ResourceIndexedSearchParamNumber(resourceName, new BigDecimal(nextValue.getValue()));
nextEntity.setResource(theEntity);
retVal.add(nextEntity);
} else {
if (!multiType) {
throw new ConfigurationException("Search param " + resourceName + " is of unexpected datatype: " + nextObject.getClass());
@ -519,15 +550,17 @@ public abstract class BaseFhirDao implements IDao {
ArrayList<ResourceIndexedSearchParamQuantity> retVal = new ArrayList<ResourceIndexedSearchParamQuantity>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource);
FhirTerser t = getContext().newTerser();
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) {
if (nextSpDef.getParamType() != SearchParamTypeEnum.QUANTITY) {
continue;
}
String nextPath = nextSpDef.getPath();
List<Object> values = t.getValues(theResource, nextPath);
for (Object nextObject : values) {
if (isBlank(nextPath)) {
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || ((IDatatype) nextObject).isEmpty()) {
continue;
}
@ -567,19 +600,18 @@ public abstract class BaseFhirDao implements IDao {
ArrayList<ResourceIndexedSearchParamString> retVal = new ArrayList<ResourceIndexedSearchParamString>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource);
FhirTerser t = getContext().newTerser();
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) {
if (nextSpDef.getParamType() != SearchParamTypeEnum.STRING) {
continue;
}
if (nextSpDef.getPath().isEmpty()) {
continue; // TODO: implement phoenetic, and any others that have
// no path
}
String nextPath = nextSpDef.getPath();
List<Object> values = t.getValues(theResource, nextPath);
for (Object nextObject : values) {
if (isBlank(nextPath)) {
// TODO: implement phoenetic, and any others that have no path
continue;
}
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null || ((IDatatype) nextObject).isEmpty()) {
continue;
}
@ -656,26 +688,25 @@ public abstract class BaseFhirDao implements IDao {
ArrayList<BaseResourceIndexedSearchParam> retVal = new ArrayList<BaseResourceIndexedSearchParam>();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource);
FhirTerser t = getContext().newTerser();
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) {
if (nextSpDef.getParamType() != SearchParamTypeEnum.TOKEN) {
continue;
}
String nextPath = nextSpDef.getPath();
if (nextPath.isEmpty()) {
if (isBlank(nextPath)) {
continue;
}
}
boolean multiType = false;
if (nextPath.endsWith("[x]")) {
multiType = true;
}
List<Object> values = t.getValues(theResource, nextPath);
List<String> systems = new ArrayList<String>();
List<String> codes = new ArrayList<String>();
for (Object nextObject : values) {
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject instanceof IdentifierDt) {
IdentifierDt nextValue = (IdentifierDt) nextObject;
if (nextValue.isEmpty()) {

View File

@ -138,8 +138,12 @@
<packageBase>ca.uhn.test.jpasrv</packageBase>
<baseResourceNames>
<baseResourceName>device</baseResourceName>
<baseResourceName>documentmanifest</baseResourceName>
<baseResourceName>documentreference</baseResourceName>
<baseResourceName>encounter</baseResourceName>
<baseResourceName>diagnosticorder</baseResourceName>
<baseResourceName>diagnosticreport</baseResourceName>
<baseResourceName>imagingstudy</baseResourceName>
<baseResourceName>location</baseResourceName>
<baseResourceName>observation</baseResourceName>
<baseResourceName>organization</baseResourceName>

View File

@ -24,6 +24,9 @@
<bean id="myDiagnosticReportDao" class="ca.uhn.fhir.jpa.dao.FhirResourceDao">
<property name="resourceType" value="ca.uhn.fhir.model.dstu.resource.DiagnosticReport"/>
</bean>
<bean id="myImagingStudyDao" class="ca.uhn.fhir.jpa.dao.FhirResourceDao">
<property name="resourceType" value="ca.uhn.fhir.model.dstu.resource.ImagingStudy"/>
</bean>
<bean id="myLocationDao" class="ca.uhn.fhir.jpa.dao.FhirResourceDao">
<property name="resourceType" value="ca.uhn.fhir.model.dstu.resource.Location"/>
</bean>
@ -43,6 +46,17 @@
<property name="resourceType" value="ca.uhn.fhir.model.dstu.resource.Encounter"/>
</bean>
<bean id="myDiagnosticOrderDao" class="ca.uhn.fhir.jpa.dao.FhirResourceDao">
<property name="resourceType" value="ca.uhn.fhir.model.dstu.resource.DiagnosticOrder"/>
</bean>
<bean id="myDocumentManifestDao" class="ca.uhn.fhir.jpa.dao.FhirResourceDao">
<property name="resourceType" value="ca.uhn.fhir.model.dstu.resource.DocumentManifest"/>
</bean>
<bean id="myDocumentReferenceDao" class="ca.uhn.fhir.jpa.dao.FhirResourceDao">
<property name="resourceType" value="ca.uhn.fhir.model.dstu.resource.DocumentReference"/>
</bean>
<bean id="myPersistenceDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" lazy-init="true">
<property name="url" value="jdbc:derby:memory:myUnitTestDB;create=true" />
<!-- <property name="url" value="jdbc:derby:directory:myUnitTestDB;create=true" /> -->

View File

@ -5,6 +5,7 @@ import static org.junit.Assert.*;
import java.util.Date;
import org.apache.commons.io.IOUtils;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
@ -22,7 +23,11 @@ import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.composite.PeriodDt;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu.resource.DiagnosticOrder;
import ca.uhn.fhir.model.dstu.resource.DocumentManifest;
import ca.uhn.fhir.model.dstu.resource.DocumentReference;
import ca.uhn.fhir.model.dstu.resource.Encounter;
import ca.uhn.fhir.model.dstu.resource.ImagingStudy;
import ca.uhn.fhir.model.dstu.resource.Location;
import ca.uhn.fhir.model.dstu.resource.Observation;
import ca.uhn.fhir.model.dstu.resource.Organization;
@ -41,7 +46,11 @@ import ca.uhn.fhir.rest.gclient.TokenClientParam;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.test.jpasrv.DiagnosticOrderResourceProvider;
import ca.uhn.test.jpasrv.DocumentManifestResourceProvider;
import ca.uhn.test.jpasrv.DocumentReferenceResourceProvider;
import ca.uhn.test.jpasrv.EncounterResourceProvider;
import ca.uhn.test.jpasrv.ImagingStudyResourceProvider;
import ca.uhn.test.jpasrv.LocationResourceProvider;
import ca.uhn.test.jpasrv.ObservationResourceProvider;
import ca.uhn.test.jpasrv.OrganizationResourceProvider;
@ -49,58 +58,108 @@ import ca.uhn.test.jpasrv.PatientResourceProvider;
public class CompleteResourceProviderTest {
private static IFhirResourceDao<Observation> observationDao;
private static ClassPathXmlApplicationContext ourAppCtx;
private static IGenericClient ourClient;
private static FhirContext ourCtx;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CompleteResourceProviderTest.class);
private static IFhirResourceDao<Observation> ourObservationDao;
private static IFhirResourceDao<Patient> ourPatientDao;
private static IFhirResourceDao<Questionnaire> ourQuestionnaireDao;
private static Server ourServer;
private static IFhirResourceDao<Patient> patientDao;
// private static JpaConformanceProvider ourConfProvider;
// @Test
// public void test01UploadTestResources() throws Exception {
//
// IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:8888/fhir/context");
//
// File[] files = new File("src/test/resources/resources").listFiles(new PatternFilenameFilter(".*patient.*"));
// for (File file : files) {
// ourLog.info("Uploading: {}", file);
// Patient patient = ourCtx.newXmlParser().parseResource(Patient.class, new FileReader(file));
// client.create(patient);
// }
//
// files = new File("src/test/resources/resources").listFiles(new PatternFilenameFilter(".*questionnaire.*"));
// for (File file : files) {
// ourLog.info("Uploading: {}", file);
// Questionnaire patient = ourCtx.newXmlParser().parseResource(Questionnaire.class, new FileReader(file));
// client.create(patient);
// }
//
// }
private static IFhirResourceDao<Questionnaire> questionnaireDao;
/**
* See issue #52
*/
@Test
public void testUpdateWithClientSuppliedIdWhichDoesntExist() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testUpdateWithClientSuppliedIdWhichDoesntExist");
public void testImagingStudyResources() throws Exception {
IGenericClient client = ourClient;
int initialSize = client.search().forResource(ImagingStudy.class).execute().size();
String resBody = IOUtils.toString(CompleteResourceProviderTest.class.getResource("/imagingstudy.json"));
client.create().resource(resBody).execute();
int newSize = client.search().forResource(ImagingStudy.class).execute().size();
assertEquals(1, newSize - initialSize);
}
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testUpdateWithClientSuppliedIdWhichDoesntExist");
MethodOutcome outcome = ourClient.update().resource(p1).withId("testUpdateWithClientSuppliedIdWhichDoesntExist").execute();
assertEquals(true, outcome.getCreated().booleanValue());
IdDt p1Id = outcome.getId();
assertThat(p1Id.getValue(), containsString("Patient/testUpdateWithClientSuppliedIdWhichDoesntExist/_history"));
Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system", "testUpdateWithClientSuppliedIdWhichDoesntExist")).encodedJson().prettyPrint().execute();
assertEquals(1, actual.size());
assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart());
/**
* See issue #52
*/
@Test
public void testDocumentManifestResources() throws Exception {
IGenericClient client = ourClient;
int initialSize = client.search().forResource(DocumentManifest.class).execute().size();
String resBody = IOUtils.toString(CompleteResourceProviderTest.class.getResource("/documentmanifest.json"));
client.create().resource(resBody).execute();
int newSize = client.search().forResource(DocumentManifest.class).execute().size();
assertEquals(1, newSize - initialSize);
}
/**
* See issue #52
*/
@Test
public void testDocumentReferenceResources() throws Exception {
IGenericClient client = ourClient;
int initialSize = client.search().forResource(DocumentReference.class).execute().size();
String resBody = IOUtils.toString(CompleteResourceProviderTest.class.getResource("/documentreference.json"));
client.create().resource(resBody).execute();
int newSize = client.search().forResource(DocumentReference.class).execute().size();
assertEquals(1, newSize - initialSize);
}
/**
* See issue #52
*/
@Test
public void testDiagnosticOrderResources() throws Exception {
IGenericClient client = ourClient;
int initialSize = client.search().forResource(DiagnosticOrder.class).execute().size();
DiagnosticOrder res = new DiagnosticOrder();
res.addIdentifier("urn:foo", "123");
client.create().resource(res).execute();
int newSize = client.search().forResource(DiagnosticOrder.class).execute().size();
assertEquals(1, newSize - initialSize);
}
private void delete(String theResourceType, String theParamName, String theParamValue) {
Bundle resources = ourClient.search().forResource(theResourceType).where(new StringClientParam(theParamName).matches().value(theParamValue)).execute();
for (IResource next : resources.toListOfResources()) {
ourLog.info("Deleting resource: {}", next.getId());
ourClient.delete().resource(next).execute();
}
}
private void deleteToken(String theResourceType, String theParamName, String theParamSystem, String theParamValue) {
Bundle resources = ourClient.search().forResource(theResourceType).where(new TokenClientParam(theParamName).exactly().systemAndCode(theParamSystem, theParamValue)).execute();
for (IResource next : resources.toListOfResources()) {
ourLog.info("Deleting resource: {}", next.getId());
ourClient.delete().resource(next).execute();
}
}
@Test
public void testCreateWithClientSuppliedId() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testCreateWithId01");
@ -130,134 +189,6 @@ public class CompleteResourceProviderTest {
assertNotNull(history.getEntries().get(0).getResource());
}
@Test
public void testTryToCreateResourceWithReferenceThatDoesntExist() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testTryToCreateResourceWithReferenceThatDoesntExist01");
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testTryToCreateResourceWithReferenceThatDoesntExist01");
p1.addName().addFamily("testTryToCreateResourceWithReferenceThatDoesntExistFamily01").addGiven("testTryToCreateResourceWithReferenceThatDoesntExistGiven01");
p1.setManagingOrganization(new ResourceReferenceDt("Organization/1323123232349875324987529835"));
try {
ourClient.create(p1).getId();
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Organization/1323123232349875324987529835"));
}
}
@Test
public void testSaveAndRetrieveExistingNarrative() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testSaveAndRetrieveExistingNarrative01");
Patient p1 = new Patient();
p1.getText().setStatus(NarrativeStatusEnum.GENERATED);
p1.getText().getDiv().setValueAsString("<div>HELLO WORLD</div>");
p1.addIdentifier().setSystem("urn:system").setValue("testSaveAndRetrieveExistingNarrative01");
IdDt newId = ourClient.create(p1).getId();
Patient actual = ourClient.read(Patient.class, newId);
assertEquals("<div xmlns=\"http://www.w3.org/1999/xhtml\">HELLO WORLD</div>", actual.getText().getDiv().getValueAsString());
}
@Test
public void testSaveAndRetrieveWithoutNarrative() {
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testSearchByResourceChain01");
IdDt newId = ourClient.create(p1).getId();
Patient actual = ourClient.read(Patient.class, newId);
assertThat(actual.getText().getDiv().getValueAsString(), containsString("<td>Identifier</td><td>testSearchByResourceChain01</td>"));
}
@Test
public void testSaveAndRetrieveWithContained() {
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testSaveAndRetrieveWithContained01");
Organization o1 = new Organization();
o1.addIdentifier().setSystem("urn:system").setValue("testSaveAndRetrieveWithContained02");
p1.getManagingOrganization().setResource(o1);
IdDt newId = ourClient.create().resource(p1).execute().getId();
Patient actual = ourClient.read(Patient.class, newId);
assertEquals(1, actual.getContained().getContainedResources().size());
assertThat(actual.getText().getDiv().getValueAsString(), containsString("<td>Identifier</td><td>testSaveAndRetrieveWithContained01</td>"));
Bundle b = ourClient.search().forResource("Patient").where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system","testSaveAndRetrieveWithContained01")).execute();
assertEquals(1, b.size());
}
@Test
public void testSearchByIdentifier() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testSearchByIdentifier01");
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testSearchByIdentifier02");
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testSearchByIdentifier01");
p1.addName().addFamily("testSearchByIdentifierFamily01").addGiven("testSearchByIdentifierGiven01");
IdDt p1Id = ourClient.create(p1).getId();
Patient p2 = new Patient();
p2.addIdentifier().setSystem("urn:system").setValue("testSearchByIdentifier02");
p2.addName().addFamily("testSearchByIdentifierFamily01").addGiven("testSearchByIdentifierGiven02");
ourClient.create(p2).getId();
Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system", "testSearchByIdentifier01")).encodedJson().prettyPrint().execute();
assertEquals(1, actual.size());
assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart());
}
@Test
public void testSearchByIdentifierWithoutSystem() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "", "testSearchByIdentifierWithoutSystem01");
Patient p1 = new Patient();
p1.addIdentifier().setValue("testSearchByIdentifierWithoutSystem01");
IdDt p1Id = ourClient.create(p1).getId();
Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode(null, "testSearchByIdentifierWithoutSystem01")).encodedJson().prettyPrint().execute();
assertEquals(1, actual.size());
assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart());
}
@Test
public void testUpdateRejectsInvalidTypes() throws InterruptedException {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testUpdateRejectsInvalidTypes");
Patient p1 = new Patient();
p1.addIdentifier("urn:system", "testUpdateRejectsInvalidTypes");
p1.addName().addFamily("Tester").addGiven("testUpdateRejectsInvalidTypes");
IdDt p1id = ourClient.create().resource(p1).execute().getId();
Organization p2 = new Organization();
p2.getName().setValue("testUpdateRejectsInvalidTypes");
try {
ourClient.update().resource(p2).withId("Organization/" + p1id.getIdPart()).execute();
fail();
} catch (UnprocessableEntityException e) {
// good
}
try {
ourClient.update().resource(p2).withId("Patient/" + p1id.getIdPart()).execute();
fail();
} catch (UnprocessableEntityException e) {
// good
}
}
@Test
public void testDeepChaining() {
delete("Location", Location.SP_NAME, "testDeepChainingL1");
@ -297,20 +228,86 @@ public class CompleteResourceProviderTest {
}
private void delete(String theResourceType, String theParamName, String theParamValue) {
Bundle resources = ourClient.search().forResource(theResourceType).where(new StringClientParam(theParamName).matches().value(theParamValue)).execute();
for (IResource next : resources.toListOfResources()) {
ourLog.info("Deleting resource: {}", next.getId());
ourClient.delete().resource(next).execute();
}
@Test
public void testSaveAndRetrieveExistingNarrative() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testSaveAndRetrieveExistingNarrative01");
Patient p1 = new Patient();
p1.getText().setStatus(NarrativeStatusEnum.GENERATED);
p1.getText().getDiv().setValueAsString("<div>HELLO WORLD</div>");
p1.addIdentifier().setSystem("urn:system").setValue("testSaveAndRetrieveExistingNarrative01");
IdDt newId = ourClient.create().resource(p1).execute().getId();
Patient actual = ourClient.read(Patient.class, newId);
assertEquals("<div xmlns=\"http://www.w3.org/1999/xhtml\">HELLO WORLD</div>", actual.getText().getDiv().getValueAsString());
}
private void deleteToken(String theResourceType, String theParamName, String theParamSystem, String theParamValue) {
Bundle resources = ourClient.search().forResource(theResourceType).where(new TokenClientParam(theParamName).exactly().systemAndCode(theParamSystem, theParamValue)).execute();
for (IResource next : resources.toListOfResources()) {
ourLog.info("Deleting resource: {}", next.getId());
ourClient.delete().resource(next).execute();
}
@Test
public void testSaveAndRetrieveWithContained() {
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testSaveAndRetrieveWithContained01");
Organization o1 = new Organization();
o1.addIdentifier().setSystem("urn:system").setValue("testSaveAndRetrieveWithContained02");
p1.getManagingOrganization().setResource(o1);
IdDt newId = ourClient.create().resource(p1).execute().getId();
Patient actual = ourClient.read(Patient.class, newId);
assertEquals(1, actual.getContained().getContainedResources().size());
assertThat(actual.getText().getDiv().getValueAsString(), containsString("<td>Identifier</td><td>testSaveAndRetrieveWithContained01</td>"));
Bundle b = ourClient.search().forResource("Patient").where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system", "testSaveAndRetrieveWithContained01")).execute();
assertEquals(1, b.size());
}
@Test
public void testSaveAndRetrieveWithoutNarrative() {
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testSearchByResourceChain01");
IdDt newId = ourClient.create().resource(p1).execute().getId();
Patient actual = ourClient.read(Patient.class, newId);
assertThat(actual.getText().getDiv().getValueAsString(), containsString("<td>Identifier</td><td>testSearchByResourceChain01</td>"));
}
@Test
public void testSearchByIdentifier() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testSearchByIdentifier01");
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testSearchByIdentifier02");
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testSearchByIdentifier01");
p1.addName().addFamily("testSearchByIdentifierFamily01").addGiven("testSearchByIdentifierGiven01");
IdDt p1Id = ourClient.create().resource(p1).execute().getId();
Patient p2 = new Patient();
p2.addIdentifier().setSystem("urn:system").setValue("testSearchByIdentifier02");
p2.addName().addFamily("testSearchByIdentifierFamily01").addGiven("testSearchByIdentifierGiven02");
ourClient.create().resource(p2).execute().getId();
Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system", "testSearchByIdentifier01")).encodedJson().prettyPrint().execute();
assertEquals(1, actual.size());
assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart());
}
@Test
public void testSearchByIdentifierWithoutSystem() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "", "testSearchByIdentifierWithoutSystem01");
Patient p1 = new Patient();
p1.addIdentifier().setValue("testSearchByIdentifierWithoutSystem01");
IdDt p1Id = ourClient.create().resource(p1).execute().getId();
Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode(null, "testSearchByIdentifierWithoutSystem01")).encodedJson().prettyPrint()
.execute();
assertEquals(1, actual.size());
assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart());
}
@Test
@ -320,19 +317,19 @@ public class CompleteResourceProviderTest {
Organization o1 = new Organization();
o1.setName("testSearchByResourceChainName01");
IdDt o1id = ourClient.create(o1).getId();
IdDt o1id = ourClient.create().resource(o1).execute().getId();
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testSearchByResourceChain01");
p1.addName().addFamily("testSearchByResourceChainFamily01").addGiven("testSearchByResourceChainGiven01");
p1.setManagingOrganization(new ResourceReferenceDt(o1id));
IdDt p1Id = ourClient.create(p1).getId();
IdDt p1Id = ourClient.create().resource(p1).execute().getId();
//@formatter:off
Bundle actual = ourClient.search()
.forResource(Patient.class)
.where(Patient.PROVIDER.hasId(o1id.getIdPart()))
.encodedJson().andLogRequestAndResponse(true).prettyPrint().execute();
.encodedJson().prettyPrint().execute();
//@formatter:on
assertEquals(1, actual.size());
assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart());
@ -341,13 +338,77 @@ public class CompleteResourceProviderTest {
actual = ourClient.search()
.forResource(Patient.class)
.where(Patient.PROVIDER.hasId(o1id.getValue()))
.encodedJson().andLogRequestAndResponse(true).prettyPrint().execute();
.encodedJson().prettyPrint().execute();
//@formatter:on
assertEquals(1, actual.size());
assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart());
}
@Test
public void testTryToCreateResourceWithReferenceThatDoesntExist() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testTryToCreateResourceWithReferenceThatDoesntExist01");
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testTryToCreateResourceWithReferenceThatDoesntExist01");
p1.addName().addFamily("testTryToCreateResourceWithReferenceThatDoesntExistFamily01").addGiven("testTryToCreateResourceWithReferenceThatDoesntExistGiven01");
p1.setManagingOrganization(new ResourceReferenceDt("Organization/1323123232349875324987529835"));
try {
ourClient.create().resource(p1).execute().getId();
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Organization/1323123232349875324987529835"));
}
}
@Test
public void testUpdateRejectsInvalidTypes() throws InterruptedException {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testUpdateRejectsInvalidTypes");
Patient p1 = new Patient();
p1.addIdentifier("urn:system", "testUpdateRejectsInvalidTypes");
p1.addName().addFamily("Tester").addGiven("testUpdateRejectsInvalidTypes");
IdDt p1id = ourClient.create().resource(p1).execute().getId();
Organization p2 = new Organization();
p2.getName().setValue("testUpdateRejectsInvalidTypes");
try {
ourClient.update().resource(p2).withId("Organization/" + p1id.getIdPart()).execute();
fail();
} catch (UnprocessableEntityException e) {
// good
}
try {
ourClient.update().resource(p2).withId("Patient/" + p1id.getIdPart()).execute();
fail();
} catch (UnprocessableEntityException e) {
// good
}
}
@Test
public void testUpdateWithClientSuppliedIdWhichDoesntExist() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testUpdateWithClientSuppliedIdWhichDoesntExist");
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testUpdateWithClientSuppliedIdWhichDoesntExist");
MethodOutcome outcome = ourClient.update().resource(p1).withId("testUpdateWithClientSuppliedIdWhichDoesntExist").execute();
assertEquals(true, outcome.getCreated().booleanValue());
IdDt p1Id = outcome.getId();
assertThat(p1Id.getValue(), containsString("Patient/testUpdateWithClientSuppliedIdWhichDoesntExist/_history"));
Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system", "testUpdateWithClientSuppliedIdWhichDoesntExist")).encodedJson()
.prettyPrint().execute();
assertEquals(1, actual.size());
assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart());
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
@ -364,17 +425,17 @@ public class CompleteResourceProviderTest {
if (true) {
ourAppCtx = new ClassPathXmlApplicationContext("fhir-spring-test-config.xml");
patientDao = (IFhirResourceDao<Patient>) ourAppCtx.getBean("myPatientDao", IFhirResourceDao.class);
ourPatientDao = (IFhirResourceDao<Patient>) ourAppCtx.getBean("myPatientDao", IFhirResourceDao.class);
PatientResourceProvider patientRp = new PatientResourceProvider();
patientRp.setDao(patientDao);
patientRp.setDao(ourPatientDao);
questionnaireDao = (IFhirResourceDao<Questionnaire>) ourAppCtx.getBean("myQuestionnaireDao", IFhirResourceDao.class);
ourQuestionnaireDao = (IFhirResourceDao<Questionnaire>) ourAppCtx.getBean("myQuestionnaireDao", IFhirResourceDao.class);
QuestionnaireResourceProvider questionnaireRp = new QuestionnaireResourceProvider();
questionnaireRp.setDao(questionnaireDao);
questionnaireRp.setDao(ourQuestionnaireDao);
observationDao = (IFhirResourceDao<Observation>) ourAppCtx.getBean("myObservationDao", IFhirResourceDao.class);
ourObservationDao = (IFhirResourceDao<Observation>) ourAppCtx.getBean("myObservationDao", IFhirResourceDao.class);
ObservationResourceProvider observationRp = new ObservationResourceProvider();
observationRp.setDao(observationDao);
observationRp.setDao(ourObservationDao);
IFhirResourceDao<Location> locationDao = (IFhirResourceDao<Location>) ourAppCtx.getBean("myLocationDao", IFhirResourceDao.class);
LocationResourceProvider locationRp = new LocationResourceProvider();
@ -388,7 +449,23 @@ public class CompleteResourceProviderTest {
OrganizationResourceProvider organizationRp = new OrganizationResourceProvider();
organizationRp.setDao(organizationDao);
restServer.setResourceProviders(encounterRp, locationRp, patientRp, questionnaireRp, observationRp, organizationRp);
IFhirResourceDao<ImagingStudy> imagingStudyDao = (IFhirResourceDao<ImagingStudy>) ourAppCtx.getBean("myImagingStudyDao", IFhirResourceDao.class);
ImagingStudyResourceProvider imagingStudyRp = new ImagingStudyResourceProvider();
imagingStudyRp.setDao(imagingStudyDao);
IFhirResourceDao<DiagnosticOrder> diagnosticOrderDao =ourAppCtx.getBean("myDiagnosticOrderDao", IFhirResourceDao.class);
DiagnosticOrderResourceProvider diagnosticOrderRp = new DiagnosticOrderResourceProvider();
diagnosticOrderRp.setDao(diagnosticOrderDao);
IFhirResourceDao<DocumentManifest> documentManifestDao =ourAppCtx.getBean("myDocumentManifestDao", IFhirResourceDao.class);
DocumentManifestResourceProvider documentManifestRp = new DocumentManifestResourceProvider();
documentManifestRp.setDao(documentManifestDao);
IFhirResourceDao<DocumentReference> documentReferenceDao =ourAppCtx.getBean("myDocumentReferenceDao", IFhirResourceDao.class);
DocumentReferenceResourceProvider documentReferenceRp = new DocumentReferenceResourceProvider();
documentReferenceRp.setDao(documentReferenceDao);
restServer.setResourceProviders(diagnosticOrderRp, documentManifestRp, documentReferenceRp, encounterRp, locationRp, patientRp, questionnaireRp, observationRp, organizationRp, imagingStudyRp);
restServer.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
IFhirSystemDao systemDao = (IFhirSystemDao) ourAppCtx.getBean("mySystemDao", IFhirSystemDao.class);
@ -414,7 +491,7 @@ public class CompleteResourceProviderTest {
}
ourCtx = restServer.getFhirContext();
// ourCtx.getRestfulClientFactory().setProxy("localhost", 8888);
// ourCtx.getRestfulClientFactory().setProxy("localhost", 8888);
ourClient = ourCtx.newRestfulGenericClient(serverBase);
// ourClient = ourCtx.newRestfulGenericClient("http://fhir.healthintersections.com.au/open");

View File

@ -0,0 +1,60 @@
{
"resourceType" : "DocumentManifest",
"text" : {
"status" : "generated",
"div" : "<div xmlns=\"http://www.w3.org/1999/xhtml\">Text</div>"
},
"contained" : [
{
"resourceType" : "Practitioner",
"id" : "a1",
"name" : {
"family" : [
"Dopplemeyer"
],
"given" : [
"Sherry"
]
},
"telecom" : [
{
"system" : "email",
"value" : "john.doe@healthcare.example.org"
}
],
"organization" : {
"display" : "Cleveland Clinic"
},
"role" : [
{
"text" : "Primary Surgon"
}
],
"specialty" : [
{
"text" : "Orthopedic"
}
]
}
],
"masterIdentifier" : {
"system" : "http://example.org/documents",
"value" : "23425234234-2346"
},
"subject" : [
],
"type" : {
"text" : "History and Physical"
},
"author" : [
{
"reference" : "#a1"
}
],
"created" : "2004-12-25T23:50:50",
"source" : "urn:oid:1.3.6.1.4.1.21367.2009.1.2.1",
"status" : "current",
"description" : "Physical",
"content" : [
]
}

View File

@ -0,0 +1,140 @@
{
"resourceType" : "DocumentReference",
"text" : {
"status" : "generated",
"div" : "<div xmlns=\"http://www.w3.org/1999/xhtml\">&#xA; <p>&#xA; <b>Generated Narrative</b>&#xA; </p>&#xA; <p>&#xA; <b>masterIdentifier</b>: urn:oid:1.3.6.1.4.1.21367.2005.3.7&#xA; </p>&#xA; <p>&#xA; <b>subject</b>: &#xA; <a href=\"Patient/xcda\">MRN = 12345 (usual); Henry Levin ; Male; birthDate: 24-Sep 1932; active</a>&#xA; </p>&#xA; <p>&#xA; <b>type</b>: &#xA; <span title=\"Codes: {http://loinc.org 34108-1}\">Outpatient Note</span>&#xA; </p>&#xA; <p>&#xA; <b>author</b>: Sherry Dopplemeyer ; Primary Surgon; Orthopedic, Gerald Smitty ; Attending; Orthopedic&#xA; </p>&#xA; <p>&#xA; <b>created</b>: 24-Dec 2005 9:35&#xA; </p>&#xA; <p>&#xA; <b>indexed</b>: 24-Dec 2005 9:43&#xA; </p>&#xA; <p>&#xA; <b>status</b>: current&#xA; </p>&#xA; <p>&#xA; <b>description</b>: Physical&#xA; </p>&#xA; <p>&#xA; <b>confidentiality</b>: &#xA; <span title=\"Codes: {http://ihe.net/xds/connectathon/confidentialityCodes 1.3.6.1.4.1.21367.2006.7.101}\">Clinical-Staff</span>&#xA; </p>&#xA; <p>&#xA; <b>primaryLanguage</b>: en-US&#xA; </p>&#xA; <p>&#xA; <b>mimeType</b>: application/hl7-v3+xml&#xA; </p>&#xA; <p>&#xA; <b>size</b>: 3654&#xA; </p>&#xA; <p>&#xA; <b>hash</b>: da39a3ee5e6b4b0d3255bfef95601890afd80709&#xA; </p>&#xA; <p>&#xA; <b>location</b>: &#xA; <a href=\"http://example.org/xds/mhd/Binary/@07a6483f-732b-461e-86b6-edb665c45510\">http://example.org/xds/mhd/Binary/@07a6483f-732b-461e-86b6-edb665c45510</a>&#xA; </p>&#xA; <h3>Contexts</h3>&#xA; <table class=\"grid\">&#xA; <tr>&#xA; <td>&#xA; <b>Event</b>&#xA; </td>&#xA; <td>&#xA; <b>Period</b>&#xA; </td>&#xA; <td>&#xA; <b>FacilityType</b>&#xA; </td>&#xA; </tr>&#xA; <tr>&#xA; <td>&#xA; <span title=\"Codes: {http://ihe.net/xds/connectathon/eventCodes T-D8200}\">Arm</span>&#xA; </td>&#xA; <td>23-Dec 2004 8:0 --&gt; 23-Dec 2004 8:1</td>&#xA; <td>&#xA; <span title=\"Codes: {http://www.ihe.net/xds/connectathon/healthcareFacilityTypeCodes Outpatient}\">Outpatient</span>&#xA; </td>&#xA; </tr>&#xA; </table>&#xA; </div>"
},
"contained" : [
{
"resourceType" : "Practitioner",
"id" : "a1",
"name" : {
"family" : [
"Dopplemeyer"
],
"given" : [
"Sherry"
]
},
"telecom" : [
{
"system" : "email",
"value" : "john.doe@healthcare.example.org"
}
],
"organization" : {
"display" : "Cleveland Clinic"
},
"role" : [
{
"text" : "Primary Surgon"
}
],
"specialty" : [
{
"text" : "Orthopedic"
}
]
},
{
"resourceType" : "Practitioner",
"id" : "a2",
"name" : {
"family" : [
"Smitty"
],
"given" : [
"Gerald"
]
},
"telecom" : [
{
"system" : "email",
"value" : "john.doe@healthcare.example.org"
}
],
"organization" : {
"display" : "Cleveland Clinic"
},
"role" : [
{
"text" : "Attending"
}
],
"specialty" : [
{
"text" : "Orthopedic"
}
]
}
],
"masterIdentifier" : {
"system" : "urn:ietf:rfc:3986",
"value" : "urn:oid:1.3.6.1.4.1.21367.2005.3.7"
},
"subject" : {
},
"type" : {
"coding" : [
{
"system" : "http://loinc.org",
"code" : "34108-1",
"display" : "Outpatient Note"
}
]
},
"author" : [
{
"reference" : "#a1"
},
{
"reference" : "#a2"
}
],
"created" : "2005-12-24T09:35:00+11:00",
"indexed" : "2005-12-24T09:43:41+11:00",
"status" : "current",
"description" : "Physical",
"confidentiality" : [
{
"coding" : [
{
"system" : "http://ihe.net/xds/connectathon/confidentialityCodes",
"code" : "1.3.6.1.4.1.21367.2006.7.101",
"display" : "Clinical-Staff"
}
]
}
],
"primaryLanguage" : "en-US",
"mimeType" : "application/hl7-v3+xml",
"size" : 3654,
"hash" : "da39a3ee5e6b4b0d3255bfef95601890afd80709",
"location" : "http://example.org/xds/mhd/Binary/@07a6483f-732b-461e-86b6-edb665c45510",
"context" : {
"event" : [
{
"coding" : [
{
"system" : "http://ihe.net/xds/connectathon/eventCodes",
"code" : "T-D8200",
"display" : "Arm"
}
]
}
],
"period" : {
"start" : "2004-12-23T08:00:00",
"end" : "2004-12-23T08:01:00"
},
"facilityType" : {
"coding" : [
{
"system" : "http://www.ihe.net/xds/connectathon/healthcareFacilityTypeCodes",
"code" : "Outpatient",
"display" : "Outpatient"
}
]
}
}
}

View File

@ -0,0 +1,2 @@
{
"resourceType" : "ImagingStudy","text" : {"status" : "generated","div" : "<div xmlns=\"http://www.w3.org/1999/xhtml\">Image 1 from Series 3: CT Images on Patient MINT (MINT1234) taken at 1-Jan 2011 01:20 AM</div>"},"dateTime" : "2011-01-01T11:01:20","uid" : "urn:oid:2.16.124.113543.6003.1154777499.30246.19789.3503430045","numberOfSeries" : 1,"numberOfInstances" : 1,"series" : [{"number" : 3,"modality" : "CT","uid" : "urn:oid:2.16.124.113543.6003.2588828330.45298.17418.2723805630","description" : "CT Surview 180","numberOfInstances" : 1,"instance" : [{"number" : 1,"uid" : "urn:oid:2.16.124.113543.6003.189642796.63084.16748.2599092903","sopclass" : "urn:oid:1.2.840.10008.5.1.4.1.1.2","url" : "http://localhost/fhir/Binary/@1.2.840.11361907579238403408700.3.0.14.19970327150033"}]}]}

View File

@ -28,7 +28,7 @@
<scope>provided</scope>
</dependency>
<!-- Testing -->
<!-- Testing -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
@ -303,6 +303,17 @@
</plugin>
</plugins>
</pluginManagement>
<resources>
<resource>
<directory>${baseDir}/src/main/resources</directory>
</resource>
<resource>
<directory>${baseDir}/target/generated-sources/tinder</directory>
</resource>
<resource>
<directory>${baseDir}/target/generated-resources/tinder</directory>
</resource>
</resources>
</build>
</project>

View File

@ -3,6 +3,8 @@ package ca.uhn.fhir.model.primitive;
import static org.junit.Assert.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import org.apache.commons.lang3.time.FastDateFormat;
@ -15,55 +17,41 @@ import ca.uhn.fhir.parser.DataFormatException;
public class BaseDateTimeDtTest {
private SimpleDateFormat myDateInstantParser;
private FastDateFormat myDateInstantZoneParser;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseDateTimeDtTest.class);
@Before
public void before() {
myDateInstantParser = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
myDateInstantZoneParser = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSSZ", TimeZone.getTimeZone("GMT-02:00"));
}
@Test
public void setTimezoneToZulu() {
DateTimeDt dt = new DateTimeDt(new Date(816411488000L));
// assertEquals("1995-11-14T23:58:08", dt.getValueAsString());
dt.setTimeZoneZulu(true);
assertEquals("1995-11-15T04:58:08Z", dt.getValueAsString());
}
@Test
public void testParseYear() throws DataFormatException {
DateTimeDt dt = new DateTimeDt();
dt.setValueAsString("2013");
public void testFormats() throws Exception {
Date instant = myDateInstantParser.parse("2001-02-03 13:01:02.555");
for (FastDateFormat next : BaseDateTimeDt.getFormatters()) {
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("EST"));
cal.setTime(instant);
String value = next.format(cal);
ourLog.info("String: {}", value);
DateTimeDt dt = new DateTimeDt(value);
String reEncoded = next.format(dt.getValue());
assertEquals(value, reEncoded);
assertEquals("2013", myDateInstantParser.format(dt.getValue()).substring(0, 4));
assertEquals("2013", dt.getValueAsString());
assertEquals(false, dt.isTimeZoneZulu());
assertNull(dt.getTimeZone());
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();
dt.setValueAsString("2013-02");
assertEquals("2013-02", myDateInstantParser.format(dt.getValue()).substring(0, 7));
assertEquals("2013-02", dt.getValueAsString());
assertEquals(false, dt.isTimeZoneZulu());
assertNull(dt.getTimeZone());
assertEquals(TemporalPrecisionEnum.MONTH, dt.getPrecision());
}
@Test
public void testParseMonthNoDashes() throws DataFormatException {
DateTimeDt dt = new DateTimeDt();
dt.setValueAsString("201302");
assertEquals("2013-02", myDateInstantParser.format(dt.getValue()).substring(0, 7));
assertEquals("2013-02", dt.getValueAsString());
assertEquals(false, dt.isTimeZoneZulu());
assertNull(dt.getTimeZone());
assertEquals(TemporalPrecisionEnum.MONTH, dt.getPrecision());
}
@Test
public void testParseDay() throws DataFormatException {
DateTimeDt dt = new DateTimeDt();
@ -75,39 +63,13 @@ public class BaseDateTimeDtTest {
assertNull(dt.getTimeZone());
assertEquals(TemporalPrecisionEnum.DAY, dt.getPrecision());
}
@Test
public void testParseSecond() throws DataFormatException {
DateTimeDt dt = new DateTimeDt();
dt.setValueAsString("2013-02-03T11:22:33");
assertEquals("2013-02-03 11:22:33", myDateInstantParser.format(dt.getValue()).substring(0, 19));
assertEquals("2013-02-03T11:22:33", dt.getValueAsString());
assertEquals(false, dt.isTimeZoneZulu());
assertNull(dt.getTimeZone());
assertEquals(TemporalPrecisionEnum.SECOND, dt.getPrecision());
}
@Test
public void testParseSecondZone() throws DataFormatException {
DateTimeDt dt = new DateTimeDt();
dt.setValueAsString("2013-02-03T11:22:33-02:00");
assertEquals("2013-02-03T11:22:33-02:00", dt.getValueAsString());
assertEquals(false, dt.isTimeZoneZulu());
assertEquals(TimeZone.getTimeZone("GMT-02:00"), dt.getTimeZone());
assertEquals(TemporalPrecisionEnum.SECOND, dt.getPrecision());
}
@Test
public void testParseSecondulu() throws DataFormatException {
DateTimeDt dt = new DateTimeDt();
dt.setValueAsString("2013-02-03T11:22:33Z");
assertEquals("2013-02-03T11:22:33Z", dt.getValueAsString());
assertEquals(true, dt.isTimeZoneZulu());
assertEquals(null, dt.getTimeZone());
assertEquals(TemporalPrecisionEnum.SECOND, dt.getPrecision());
@Test()
public void testParseMalformatted() throws DataFormatException {
DateTimeDt dt = new DateTimeDt("20120102");
assertEquals("20120102", dt.getValueAsString());
assertEquals("2012-01-02", new SimpleDateFormat("yyyy-MM-dd").format(dt.getValue()));
}
@Test
@ -120,8 +82,8 @@ public class BaseDateTimeDtTest {
assertEquals(false, dt.isTimeZoneZulu());
assertNull(dt.getTimeZone());
assertEquals(TemporalPrecisionEnum.MILLI, dt.getPrecision());
}
}
@Test
public void testParseMilliZone() throws DataFormatException {
InstantDt dt = new InstantDt();
@ -145,4 +107,86 @@ public class BaseDateTimeDtTest {
assertNull(dt.getTimeZone());
assertEquals(TemporalPrecisionEnum.MILLI, dt.getPrecision());
}
@Test
public void testParseMonth() throws DataFormatException {
DateTimeDt dt = new DateTimeDt();
dt.setValueAsString("2013-02");
assertEquals("2013-02", myDateInstantParser.format(dt.getValue()).substring(0, 7));
assertEquals("2013-02", dt.getValueAsString());
assertEquals(false, dt.isTimeZoneZulu());
assertNull(dt.getTimeZone());
assertEquals(TemporalPrecisionEnum.MONTH, dt.getPrecision());
}
@Test
public void testParseMonthNoDashes() throws DataFormatException {
DateTimeDt dt = new DateTimeDt();
dt.setValueAsString("201302");
assertEquals("2013-02", myDateInstantParser.format(dt.getValue()).substring(0, 7));
assertEquals("201302", dt.getValueAsString());
assertEquals(false, dt.isTimeZoneZulu());
assertNull(dt.getTimeZone());
assertEquals(TemporalPrecisionEnum.MONTH, dt.getPrecision());
}
@Test
public void testParseSecond() throws DataFormatException {
DateTimeDt dt = new DateTimeDt();
dt.setValueAsString("2013-02-03T11:22:33");
assertEquals("2013-02-03 11:22:33", myDateInstantParser.format(dt.getValue()).substring(0, 19));
assertEquals("2013-02-03T11:22:33", dt.getValueAsString());
assertEquals(false, dt.isTimeZoneZulu());
assertNull(dt.getTimeZone());
assertEquals(TemporalPrecisionEnum.SECOND, dt.getPrecision());
}
@Test
public void testParseSecondulu() throws DataFormatException {
DateTimeDt dt = new DateTimeDt();
dt.setValueAsString("2013-02-03T11:22:33Z");
assertEquals("2013-02-03T11:22:33Z", dt.getValueAsString());
assertEquals(true, dt.isTimeZoneZulu());
assertEquals(null, dt.getTimeZone());
assertEquals(TemporalPrecisionEnum.SECOND, dt.getPrecision());
}
@Test
public void testParseSecondZone() throws DataFormatException {
DateTimeDt dt = new DateTimeDt();
dt.setValueAsString("2013-02-03T11:22:33-02:00");
assertEquals("2013-02-03T11:22:33-02:00", dt.getValueAsString());
assertEquals(false, dt.isTimeZoneZulu());
assertEquals(TimeZone.getTimeZone("GMT-02:00"), dt.getTimeZone());
assertEquals(TemporalPrecisionEnum.SECOND, dt.getPrecision());
}
@Test
public void testParseYear() throws DataFormatException {
DateTimeDt dt = new DateTimeDt();
dt.setValueAsString("2013");
assertEquals("2013", myDateInstantParser.format(dt.getValue()).substring(0, 4));
assertEquals("2013", dt.getValueAsString());
assertEquals(false, dt.isTimeZoneZulu());
assertNull(dt.getTimeZone());
assertEquals(TemporalPrecisionEnum.YEAR, dt.getPrecision());
}
@Test
public void testSetValueByString() {
InstantDt i = new InstantDt();
i.setValueAsString("2014-06-20T20:22:09Z");
assertNotNull(i.getValue());
assertNotNull(i.getValueAsString());
assertEquals(1403295729000L, i.getValue().getTime());
assertEquals("2014-06-20T20:22:09Z",i.getValueAsString());
}
}

View File

@ -31,7 +31,11 @@ public class XhtmlDtTest {
XhtmlDt x = new XhtmlDt();
x.setValueAsString(div);
String actual = x.getValueAsString();
XhtmlDt x2 = new XhtmlDt();
x2.setValue(x.getValue());
String actual = x2.getValueAsString();
ourLog.info("Expected {}", div.replace("\r", "").replace("\n", "\\n"));
ourLog.info("Actual {}", actual.replace("\r\n", "\\r\\n").replace("\n", "\\n"));
@ -50,6 +54,15 @@ public class XhtmlDtTest {
assertEquals("<div>amp &amp;</div>", x.getValueAsString());
}
@Test
public void testOnlyProcessingDirective() {
XhtmlDt x = new XhtmlDt();
x.setValueAsString("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
assertEquals(null, x.getValue());
assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>", x.getValueAsString());
}
@Test
public void testCharacterEntities() {
@ -60,7 +73,11 @@ public class XhtmlDtTest {
// <div>Sect: § uuml: ü Ü</div>
// <div>Sect: &sect; uuml: &uuml; &Uuml;</div>
assertEquals("<div>Sect: § uuml: ü Ü</div>", x.getValueAsString());
assertEquals("<div>"+input+"</div>", x.getValueAsString());
XhtmlDt x2 = new XhtmlDt();
x2.setValue(x.getValue());
assertEquals("<div>Sect: § uuml: ü Ü</div>", x2.getValueAsString());
}

View File

@ -28,7 +28,7 @@ public class CustomThymeleafNarrativeGeneratorTest {
String actual = narrative.getDiv().getValueAsString();
ourLog.info(actual);
assertThat(actual, containsString("<h1>Name</h1><div class=\"nameElement\"> given <b>FAM1 </b></div><h1>Address</h1><div><span>line1 </span><br/><span>line2 </span><br/></div></div>"));
assertThat(actual, containsString("<h1>Name</h1><div class=\"nameElement\"> given <b>FAM1 </b></div><h1>Address</h1><div><span>line1 </span><br /><span>line2 </span><br /></div></div>"));
}
}

View File

@ -12,13 +12,20 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.dstu.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu.composite.CodingDt;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu.resource.Composition;
import ca.uhn.fhir.model.dstu.resource.Composition.Section;
import ca.uhn.fhir.model.dstu.resource.Condition;
import ca.uhn.fhir.model.dstu.resource.DiagnosticReport;
import ca.uhn.fhir.model.dstu.resource.Observation;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.resource.Practitioner;
import ca.uhn.fhir.model.dstu.valueset.AdministrativeGenderCodesEnum;
@ -26,6 +33,7 @@ import ca.uhn.fhir.model.dstu.valueset.ConditionStatusEnum;
import ca.uhn.fhir.model.dstu.valueset.NameUseEnum;
import ca.uhn.fhir.model.dstu.valueset.PractitionerRoleEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.server.RestfulServer;
/**
* Initially contributed by Alexander Kley for bug #29
@ -155,5 +163,84 @@ public class ContainedResourceEncodingTest {
Assert.assertArrayEquals(expectedCompXml.getBytes(), actualCompXml.getBytes());
}
@Test
public void testBundleWithContained() {
DiagnosticReport dr = new DiagnosticReport();
dr.setId(new IdDt("DiagnosticReport","123"));
Observation observation = new Observation();
CodeableConceptDt obsName = new CodeableConceptDt();
obsName.setText("name");
observation.setName(obsName);
ResourceReferenceDt result = dr.addResult();
result.setResource(observation);
ArrayList<ResourceReferenceDt> performers = new ArrayList<ResourceReferenceDt>();
ResourceReferenceDt performer = new ResourceReferenceDt();
Practitioner p = new Practitioner();
p.setId(new IdDt(UUID.randomUUID().toString()));
p.addIdentifier().setSystem("DoctorID").setValue("4711");
p.addRole(PractitionerRoleEnum.DOCTOR);
p.setName(new HumanNameDt().addFamily("Mueller").addGiven("Klaus").addPrefix("Prof. Dr."));
performer.setResource(p);
performers.add(performer);
observation.setPerformer(performers);
List<IResource> list = new ArrayList<IResource>();
list.add(dr);
Bundle bundle = RestfulServer.createBundleFromResourceList(new FhirContext(), null, list, null, null, 0);
IParser parser = this.ctx.newXmlParser().setPrettyPrint(true);
String xml = parser.encodeBundleToString(bundle);
Assert.assertTrue(xml.contains("Mueller"));
}
@Test
public void testBundleWithContainedWithNoIdDt() {
DiagnosticReport dr = new DiagnosticReport();
dr.setId(new IdDt("DiagnosticReport","123"));
Observation observation = new Observation();
CodeableConceptDt obsName = new CodeableConceptDt();
obsName.setText("name");
observation.setName(obsName);
ResourceReferenceDt result = dr.addResult();
result.setResource(observation);
ArrayList<ResourceReferenceDt> performers = new ArrayList<ResourceReferenceDt>();
ResourceReferenceDt performer = new ResourceReferenceDt();
Practitioner p = new Practitioner();
// no idDt on practitioner p
p.addIdentifier().setSystem("DoctorID").setValue("4711");
p.addRole(PractitionerRoleEnum.DOCTOR);
p.setName(new HumanNameDt().addFamily("Mueller").addGiven("Klaus").addPrefix("Prof. Dr."));
performer.setResource(p);
performers.add(performer);
observation.setPerformer(performers);
List<IResource> list = new ArrayList<IResource>();
list.add(dr);
Bundle bundle = RestfulServer.createBundleFromResourceList(new FhirContext(), null, list, null, null, 0);
IParser parser = this.ctx.newXmlParser().setPrettyPrint(true);
String xml = parser.encodeBundleToString(bundle);
Assert.assertTrue(xml.contains("Mueller"));
}
}

View File

@ -18,6 +18,7 @@ import net.sf.json.JSON;
import net.sf.json.JSONSerializer;
import org.apache.commons.io.IOUtils;
import org.hamcrest.Matchers;
import org.hamcrest.core.IsNot;
import org.hamcrest.core.StringContains;
import org.hamcrest.text.StringContainsInOrder;
@ -257,9 +258,11 @@ public class JsonParserTest {
//@formatter:on
Patient res = (Patient) ourCtx.newJsonParser().parseResource(text);
String value = res.getText().getDiv().getValueAsString();
XhtmlDt div = res.getText().getDiv();
String value = div.getValueAsString();
assertNull(value);
assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>", value);
assertEquals(null, div.getValue());
}
@Test
@ -612,7 +615,7 @@ public class JsonParserTest {
JSON expected = JSONSerializer.toJSON(msg.trim());
JSON actual = JSONSerializer.toJSON(encoded.trim());
String exp = expected.toString().replace("\\r\\n", "\\n").replace("&sect;", "§");
String exp = expected.toString().replace("\\r\\n", "\\n"); // .replace("&sect;", "§");
String act = actual.toString().replace("\\r\\n", "\\n");
ourLog.info("Expected: {}", exp);

View File

@ -1131,7 +1131,7 @@ public class XmlParserTest {
assertEquals("FHIR Core Valuesets", bundle.getTitle().getValue());
assertEquals("http://hl7.org/implement/standards/fhir/valuesets.xml", bundle.getLinkSelf().getValue());
assertEquals("2014-02-10T04:11:24.435+00:00", bundle.getUpdated().getValueAsString());
assertEquals("2014-02-10T04:11:24.435-00:00", bundle.getUpdated().getValueAsString());
assertEquals(1, bundle.getEntries().size());
BundleEntry entry = bundle.getEntries().get(0);

View File

@ -4,6 +4,7 @@ import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.io.StringReader;
import java.net.URLEncoder;
import java.nio.charset.Charset;
@ -23,6 +24,7 @@ import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicStatusLine;
import org.apache.http.util.EncodingUtils;
import org.hamcrest.core.StringContains;
import org.junit.Before;
import org.junit.BeforeClass;
@ -177,25 +179,133 @@ public class GenericClientTest {
assertEquals("44", outcome.getId().getIdPart());
assertEquals("22", outcome.getId().getVersionIdPart());
int count = 0;
assertEquals("http://example.com/fhir/Patient", capt.getValue().getURI().toString());
assertEquals("POST", capt.getValue().getMethod());
Header catH = capt.getValue().getFirstHeader("Category");
assertNotNull(Arrays.asList(capt.getValue().getAllHeaders()).toString(), catH);
assertEquals("urn:happytag; label=\"This is a happy resource\"; scheme=\"http://hl7.org/fhir/tag\"", catH.getValue());
assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType(), capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
count++;
/*
* Try fluent options
*/
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
client.create().resource(p1).withId("123").execute();
assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(1).getURI().toString());
assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType(), capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
count++;
String resourceText = "<Patient xmlns=\"http://hl7.org/fhir\"> </Patient>";
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
client.create().resource(resourceText).withId("123").execute();
assertEquals("http://example.com/fhir/Patient/123", capt.getAllValues().get(2).getURI().toString());
assertEquals(resourceText, IOUtils.toString(((HttpPost) capt.getAllValues().get(2)).getEntity().getContent()));
assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType(), capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
count++;
}
@Test
public void testCreateWithStringAutoDetectsEncoding() throws Exception {
Patient p1 = new Patient();
p1.addIdentifier("foo:bar", "12345");
p1.addName().addFamily("Smith").addGiven("John");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK"));
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") });
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
int count = 0;
client.create().resource(myCtx.newXmlParser().encodeResourceToString(p1)).execute();
assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType(), capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
assertThat(extractBody(capt, count), containsString("value=\"John\""));
count++;
client.create().resource(myCtx.newJsonParser().encodeResourceToString(p1)).execute();
assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.JSON.getResourceContentType(), capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
assertThat(extractBody(capt, count), containsString("[\"John\"]"));
count++;
/*
* e.g. Now try with reversed encoding (provide a string that's in JSON and ask the client to use XML)
*/
client.create().resource(myCtx.newXmlParser().encodeResourceToString(p1)).encodedJson().execute();
assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.JSON.getResourceContentType(), capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
assertThat(extractBody(capt, count), containsString("[\"John\"]"));
count++;
client.create().resource(myCtx.newJsonParser().encodeResourceToString(p1)).encodedXml().execute();
assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType(), capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
assertThat(extractBody(capt, count), containsString("value=\"John\""));
count++;
}
private String extractBody(ArgumentCaptor<HttpUriRequest> capt, int count) throws IOException {
String body = IOUtils.toString(((HttpEntityEnclosingRequestBase) capt.getAllValues().get(count)).getEntity().getContent());
return body;
}
@Test
public void testUpdateWithStringAutoDetectsEncoding() throws Exception {
Patient p1 = new Patient();
p1.addIdentifier("foo:bar", "12345");
p1.addName().addFamily("Smith").addGiven("John");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK"));
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") });
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
int count = 0;
client.update().resource(myCtx.newXmlParser().encodeResourceToString(p1)).withId("1").execute();
assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType(), capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
assertThat(extractBody(capt, count), containsString("value=\"John\""));
count++;
client.update().resource(myCtx.newJsonParser().encodeResourceToString(p1)).withId("1").execute();
assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.JSON.getResourceContentType(), capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
assertThat(extractBody(capt, count), containsString("[\"John\"]"));
count++;
/*
* e.g. Now try with reversed encoding (provide a string that's in JSON and ask the client to use XML)
*/
client.update().resource(myCtx.newXmlParser().encodeResourceToString(p1)).withId("1").encodedJson().execute();
assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.JSON.getResourceContentType(), capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
assertThat(extractBody(capt, count), containsString("[\"John\"]"));
count++;
client.update().resource(myCtx.newJsonParser().encodeResourceToString(p1)).withId("1").encodedXml().execute();
assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType(), capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
assertThat(extractBody(capt, count), containsString("value=\"John\""));
count++;
}
@Test
@ -503,8 +613,7 @@ public class GenericClientTest {
capt.getValue().getURI().toString());
}
@Test
public void testSearchWithAbsoluteUrl() throws Exception {
@ -518,7 +627,9 @@ public class GenericClientTest {
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
Bundle response = client.search(new UriDt("http://example.com/fhir/Patient?birthdate=%3C%3D2012-01-22&birthdate=%3E2011-01-01&_include=Patient.managingOrganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_count=123&_format=json"));
Bundle response = client
.search(new UriDt(
"http://example.com/fhir/Patient?birthdate=%3C%3D2012-01-22&birthdate=%3E2011-01-01&_include=Patient.managingOrganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_count=123&_format=json"));
assertEquals(
"http://example.com/fhir/Patient?birthdate=%3C%3D2012-01-22&birthdate=%3E2011-01-01&_include=Patient.managingOrganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_count=123&_format=json",
@ -527,7 +638,6 @@ public class GenericClientTest {
assertEquals(1, response.size());
}
@Test
public void testSearchWithAbsoluteUrlAndType() throws Exception {
@ -541,7 +651,10 @@ public class GenericClientTest {
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
Bundle response = client.search(Patient.class, new UriDt("http://example.com/fhir/Patient?birthdate=%3C%3D2012-01-22&birthdate=%3E2011-01-01&_include=Patient.managingOrganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_count=123&_format=json"));
Bundle response = client
.search(Patient.class,
new UriDt(
"http://example.com/fhir/Patient?birthdate=%3C%3D2012-01-22&birthdate=%3E2011-01-01&_include=Patient.managingOrganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_count=123&_format=json"));
assertEquals(
"http://example.com/fhir/Patient?birthdate=%3C%3D2012-01-22&birthdate=%3E2011-01-01&_include=Patient.managingOrganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_count=123&_format=json",
@ -549,7 +662,7 @@ public class GenericClientTest {
assertEquals(1, response.size());
}
@SuppressWarnings("unused")
@Test
public void testSearchByNumberExact() throws Exception {
@ -975,19 +1088,26 @@ public class GenericClientTest {
p1.setId("44");
client.update().resource(p1).execute();
int count = 0;
assertEquals(1, capt.getAllValues().size());
assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType(), capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
count++;
MethodOutcome outcome = client.update().resource(p1).execute();
assertEquals("44", outcome.getId().getIdPart());
assertEquals("22", outcome.getId().getVersionIdPart());
assertEquals(2, capt.getAllValues().size());
assertEquals("http://example.com/fhir/Patient/44", capt.getValue().getURI().toString());
assertEquals("PUT", capt.getValue().getMethod());
Header catH = capt.getValue().getFirstHeader("Category");
assertNotNull(Arrays.asList(capt.getValue().getAllHeaders()).toString(), catH);
assertEquals("urn:happytag; label=\"This is a happy resource\"; scheme=\"http://hl7.org/fhir/tag\"", catH.getValue());
assertEquals(1, capt.getAllValues().get(count).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType(), capt.getAllValues().get(count).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
/*
* Try fluent options

View File

@ -25,9 +25,11 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
import ca.uhn.fhir.model.primitive.IdDt;
@ -223,6 +225,27 @@ public class ServerFeaturesTest {
}
@Test
public void testSearchReturnWithAbsoluteIdSpecified() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/?_query=findPatientsWithAbsoluteIdSpecified");
httpGet.addHeader("Accept", Constants.CT_FHIR_XML + "; pretty=true");
CloseableHttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = servlet.getFhirContext().newXmlParser().parseBundle(responseContent);
assertEquals(2,bundle.size());
assertEquals("http://absolute.com/Patient/123", bundle.getEntries().get(0).getId().getValue());
assertEquals("http://absolute.com/Patient/123/_history/22", bundle.getEntries().get(0).getLinkSelf().getValue());
assertEquals("http://foo.com/Organization/222",bundle.getEntries().get(1).getId().getValue());
assertEquals("http://foo.com/Organization/222/_history/333",bundle.getEntries().get(1).getLinkSelf().getValue());
}
@Test
public void testSearchWithWildcardRetVal() throws Exception {
@ -345,6 +368,20 @@ public class ServerFeaturesTest {
return Collections.singletonList(p);
}
@Search(queryName = "findPatientsWithAbsoluteIdSpecified")
public List<Patient> findPatientsWithAbsoluteIdSpecified() {
Patient p = new Patient();
p.addIdentifier().setSystem("foo");
p.setId("http://absolute.com/Patient/123/_history/22");
Organization o = new Organization();
o.setId("http://foo.com/Organization/222/_history/333");
p.getManagingOrganization().setResource(o);
return Collections.singletonList(p);
}
@Override
public Class<Patient> getResourceType() {
return Patient.class;

View File

@ -5,10 +5,14 @@ import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.ContactSystemEnum;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import org.apache.commons.io.IOUtils;
import org.hamcrest.core.StringContains;
import org.junit.Test;
import java.io.IOException;
import java.text.SimpleDateFormat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
@ -31,7 +35,7 @@ public class ResourceValidatorTest {
FhirValidator val = ourCtx.newValidator();
val.setValidateAgainstStandardSchema(true);
val.setValidateAgainstStandardSchematron(false);
val.validate(p);
p.getAnimal().getBreed().setText("The Breed");
@ -45,18 +49,43 @@ public class ResourceValidatorTest {
}
}
/**
* See issue #50
*/
@Test
public void testOutOfBoundsDate() {
Patient p = new Patient();
p.setBirthDate(new DateTimeDt("2000-15-31"));
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(p);
ourLog.info(encoded);
assertThat(encoded, StringContains.containsString("2000-15-31"));
p = ourCtx.newXmlParser().parseResource(Patient.class, encoded);
assertEquals("2000-15-31", p.getBirthDate().getValueAsString());
assertEquals("2001-03-31", new SimpleDateFormat("yyyy-MM-dd").format(p.getBirthDate().getValue()));
ValidationResult result = ourCtx.newValidator().validateWithResult(p);
String resultString = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.getOperationOutcome());
ourLog.info(resultString);
assertEquals(2, result.getOperationOutcome().getIssue().size());
assertThat(resultString, StringContains.containsString("cvc-datatype-valid.1.2.3"));
}
@Test
public void testSchemaBundleValidator() throws IOException {
String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("atom-document-large.xml"));
Bundle b = ourCtx.newXmlParser().parseBundle(res);
FhirValidator val = createFhirValidator();
FhirValidator val = createFhirValidator();
val.validate(b);
Patient p = (Patient) b.getEntries().get(0).getResource();
p.getTelecomFirstRep().setValue("123-4567");
try {
val.validate(b);
fail();
@ -76,60 +105,63 @@ public class ResourceValidatorTest {
val.setValidateAgainstStandardSchema(false);
val.setValidateAgainstStandardSchematron(true);
ValidationResult validationResult = val.validateWithResult(p);
assertTrue(validationResult.isSuccessful());
ValidationResult validationResult = val.validateWithResult(p);
assertTrue(validationResult.isSuccessful());
p.getTelecomFirstRep().setValue("123-4567");
validationResult = val.validateWithResult(p);
assertFalse(validationResult.isSuccessful());
OperationOutcome operationOutcome = (OperationOutcome) validationResult.getOperationOutcome();
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome));
assertEquals(1, operationOutcome.getIssue().size());
assertThat(operationOutcome.getIssueFirstRep().getDetails().getValue(), containsString("Inv-2: A system is required if a value is provided."));
p.getTelecomFirstRep().setValue("123-4567");
validationResult = val.validateWithResult(p);
assertFalse(validationResult.isSuccessful());
OperationOutcome operationOutcome = (OperationOutcome) validationResult.getOperationOutcome();
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome));
assertEquals(1, operationOutcome.getIssue().size());
assertThat(operationOutcome.getIssueFirstRep().getDetails().getValue(), containsString("Inv-2: A system is required if a value is provided."));
p.getTelecomFirstRep().setSystem(ContactSystemEnum.EMAIL);
validationResult = val.validateWithResult(p);
assertTrue(validationResult.isSuccessful());
validationResult = val.validateWithResult(p);
assertTrue(validationResult.isSuccessful());
}
@Test
public void testSchemaBundleValidatorIsSuccessful() throws IOException {
String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("atom-document-large.xml"));
Bundle b = ourCtx.newXmlParser().parseBundle(res);
@Test
public void testSchemaBundleValidatorIsSuccessful() throws IOException {
String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("atom-document-large.xml"));
Bundle b = ourCtx.newXmlParser().parseBundle(res);
FhirValidator val = createFhirValidator();
FhirValidator val = createFhirValidator();
ValidationResult result = val.validateWithResult(b);
assertTrue(result.isSuccessful());
OperationOutcome operationOutcome = (OperationOutcome) result.getOperationOutcome();
assertNotNull(operationOutcome);
assertEquals(0, operationOutcome.getIssue().size());
}
ValidationResult result = val.validateWithResult(b);
OperationOutcome operationOutcome = (OperationOutcome) result.getOperationOutcome();
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome));
assertTrue(result.isSuccessful());
assertNotNull(operationOutcome);
assertEquals(0, operationOutcome.getIssue().size());
}
@Test
public void testSchemaBundleValidatorFails() throws IOException {
String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("atom-document-large.xml"));
Bundle b = ourCtx.newXmlParser().parseBundle(res);
@Test
public void testSchemaBundleValidatorFails() throws IOException {
String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("atom-document-large.xml"));
Bundle b = ourCtx.newXmlParser().parseBundle(res);
FhirValidator val = createFhirValidator();
FhirValidator val = createFhirValidator();
ValidationResult validationResult = val.validateWithResult(b);
assertTrue(validationResult.isSuccessful());
ValidationResult validationResult = val.validateWithResult(b);
assertTrue(validationResult.isSuccessful());
Patient p = (Patient) b.getEntries().get(0).getResource();
p.getTelecomFirstRep().setValue("123-4567");
validationResult = val.validateWithResult(b);
assertFalse(validationResult.isSuccessful());
OperationOutcome operationOutcome = (OperationOutcome) validationResult.getOperationOutcome();
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome));
assertEquals(1, operationOutcome.getIssue().size());
assertThat(operationOutcome.getIssueFirstRep().getDetails().getValue(), containsString("Inv-2: A system is required if a value is provided."));
}
Patient p = (Patient) b.getEntries().get(0).getResource();
p.getTelecomFirstRep().setValue("123-4567");
validationResult = val.validateWithResult(b);
assertFalse(validationResult.isSuccessful());
OperationOutcome operationOutcome = (OperationOutcome) validationResult.getOperationOutcome();
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome));
assertEquals(1, operationOutcome.getIssue().size());
assertThat(operationOutcome.getIssueFirstRep().getDetails().getValue(), containsString("Inv-2: A system is required if a value is provided."));
}
private FhirValidator createFhirValidator() {
FhirValidator val = ourCtx.newValidator();
val.setValidateAgainstStandardSchema(true);
val.setValidateAgainstStandardSchematron(true);
return val;
}
private FhirValidator createFhirValidator() {
FhirValidator val = ourCtx.newValidator();
val.setValidateAgainstStandardSchema(true);
val.setValidateAgainstStandardSchematron(true);
return val;
}
}

View File

@ -237,7 +237,7 @@
</coding>
<text value="Male" />
</gender>
<birthDate value="19840101" />
<birthDate value="1984-01-01" />
<address>
<text value="4016 Canal St." />
<city value="New Orleans" />
@ -284,7 +284,7 @@
</coding>
<text value="Male" />
</gender>
<birthDate value="19840101" />
<birthDate value="1984-01-01" />
<address>
<text value="4016 Canal St." />
<city value="New Orleans" />
@ -362,7 +362,7 @@
</coding>
<text value="Male" />
</gender>
<birthDate value="19840101" />
<birthDate value="1984-01-01" />
<address>
<text value="4016 Canal St." />
<city value="New Orleans" />
@ -409,7 +409,7 @@
</coding>
<text value="Male" />
</gender>
<birthDate value="19840101" />
<birthDate value="1984-01-01" />
<address>
<text value="4016 Canal St." />
<city value="New Orleans" />
@ -456,7 +456,7 @@
</coding>
<text value="Male" />
</gender>
<birthDate value="19840101" />
<birthDate value="1984-01-01" />
<address>
<text value="4016 Canal St." />
<city value="New Orleans" />

View File

@ -0,0 +1,3 @@
.classpath
.project
/target/

View File

@ -0,0 +1,91 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--
HAPI projects use the Sonatype OSS parent project.
You do not need to use this in your own projects.
-->
<parent>
<groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId>
<version>7</version>
</parent>
<groupId>ca.uhn.hapi.example</groupId>
<artifactId>hapi-fhir-example-skeleton-project</artifactId>
<version>0.8-SNAPSHOT</version>
<packaging>jar</packaging>
<name>HAPI FHIR Example - Skeleton Project</name>
<repositories>
<repository>
<id>oss-snapshots</id>
<snapshots>
<enabled>true</enabled>
</snapshots>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</repository>
</repositories>
<dependencies>
<!-- This dependency includes the core HAPI-FHIR classes -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId>
<version>0.8-SNAPSHOT</version>
</dependency>
<!-- At least one "structures" JAR must also be included -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu</artifactId>
<version>0.8-SNAPSHOT</version>
</dependency>
<!--
HAPI-FHIR uses Logback for logging support. The logback library is included
automatically by Maven as a part of the hapi-fhir-base dependency, but you
also need to include a logging library. Logback is used here, but log4j
would also be fine.
-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--
Tell Maven which Java source version you want to use
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<!--
This plugin is just a part of the HAPI internal build process, you do not
need to incude it in your own projects
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>false</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,24 @@
package ca.uhn.fhir.example;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Patient;
@SuppressWarnings("unused")
public class Example01_CreateAPatient {
public static void main(String[] theArgs) {
Patient pat = new Patient();
IdentifierDt identifier = pat.addIdentifier();
identifier.setSystem("http://acme.org/MRNs").setValue("7000135");
HumanNameDt name = pat.addName();
name.addFamily("Simpson").addGiven("Homer").addGiven("J");
pat.getBirthDate().set
}
}

View File

@ -1,7 +1,6 @@
package ca.uhn.fhir.tinder.model;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -11,6 +10,7 @@ import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.model.api.BasePrimitive;
import ca.uhn.fhir.model.api.IDatatype;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.primitive.IdDt;
public abstract class Child extends BaseElement {
@ -164,6 +164,10 @@ public abstract class Child extends BaseElement {
public String getPrimitiveType() throws ClassNotFoundException {
String name = "ca.uhn.fhir.model.primitive." + getSingleType();
Class<?> clazz = Class.forName(name);
if (clazz.equals(IdDt.class)) {
return String.class.getSimpleName();
}
while (!clazz.getSuperclass().equals(BasePrimitive.class)) {
clazz = clazz.getSuperclass();
if (clazz.equals(Object.class)) {

View File

@ -70,6 +70,7 @@
<organization>Boston Children's Hospital</organization>
</developer>
<developer>
<id>lmds</id>
<name>Laura MacDougall Sookraj</name>
<organization>University Health Network</organization>
</developer>

View File

@ -7,7 +7,7 @@
</properties>
<body>
<release version="0.8" date="TBD">
<action>
<action tyle="add">
<![CDATA[<b>API CHANGE:</b>]]> The "FHIR structures" for DSTU1 (the classes which model the
resources and composite datatypes) have been moved out of the core JAR into their
own JAR, in order to allow support for DEV resources, and DSTU2 resources when thast
@ -85,7 +85,47 @@
<action type="fix">
Resource fields with a type of "*" (or Any) sometimes failed to parse if a
value type of "code" was used. Thanks to Bill de Beaubien for reporting!
</action>
</action>
<action type="add" dev="lmds">
Remove dependency on JAXB libraries, which were used to parse and encode
dates and times (even in the JSON parser). JAXB is built in to most JDKs
but the version bundled with IBM's JDK is flaky and resulted in a number
of problems when deploying to Websphere.
</action>
<action type="fix" issue="50">
Primitive datatypes now preserve their original string value when parsing resources,
as well as containing the "parsed value". For instance, a DecimalDt field value of
<![CDATA[<code>1.0000</code>]]> will be parsed into the corresponding
decimal value, but will also retain the original value with the corresponding
level of precision. This allows vadliator rules to be applied to
original values as received "over the wire", such as well formatted but
invalid dates, e.g. "2001-15-01". Thanks to Joe Athman for reporting and
helping to come up with a fix!
</action>
<action type="add">
When using Generic Client, if performing a
<![CDATA[create]]> or <![CDATA[update]]> operation using a String as the resource body,
the client will auto-detect the FHIR encoding style and send an appropriate Content-Type header.
</action>
<action type="fix" issue="52">
JPA module (and public HAPI-FHIR test server) were unable to process resource types
where at least one search parameter has no path specified. These now correctly save
(although the server does not yet process these params, and it should). Thanks to
GitHub user shvoidlee for reporting and help with analysis!
</action>
<action type="fix">
Generic/Fluent Client "create" and "update" method requests were not setting a content type header
</action>
<action type="add" issue="53" dev="petromykhailysyn">
DateDt left precision value as null in the constructor
<![CDATA[DateDt(Date)]]>.
</action>
<action type="fix">
RESTful server now doesn't overwrite resource IDs if they are absolute. In other words, if
a server's Resource Provider returns a resource with ID "Patient/123" it will be translated to
"[base url]/Patient/123" but if the RP returns ID "http://foo/Patient/123" the ID will be
returned exactly as is. Thanks to Bill de Beaubien for the suggestion!
</action>
</release>
<release version="0.7" date="2014-Oct-23">
<action type="add" issue="30">

View File

@ -1,476 +0,0 @@
<?xml version="1.0"?>
<document xmlns="http://maven.apache.org/changes/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 ./changes.xsd">
<properties>
<author>James Agnew</author>
<title>HAPI FHIR Changelog</title>
</properties>
<body>
<release version="0.8" date="TBD">
<<<<<<< HEAD:src/changes/changes.xml
<action type="add" issue="38">
Profile generation on the server was not working due to IdDt being
incorrectly used. Thanks to Bill de Beaubien for the pull request!
</action>
<action type="add" issue="42">
=======
<action>
<![CDATA[<b>API CHANGE:</b>]]> The "FHIR structures" for DSTU1 (the classes which model the
resources and composite datatypes) have been moved out of the core JAR into their
own JAR, in order to allow support for DEV resources, and DSTU2 resources when thast
version is finalized. See
<![CDATA[<a href="./doc_upgrading.html">upgrading</a>]]>
for more information.
</action>
<action type="add" issue="38" dev="wdebeau1">
Profile generation on the server was not working due to IdDt being
incorrectly used. Thanks to Bill de Beaubien for the pull request!
</action>
<action type="add" issue="42" dev="wdebeau1">
>>>>>>> d22a35788f57e9f7ce64bc8afc2ee7eaf29d94f2:src/changes/changes.xml
Profiles did not generate correctly if a resource definition class had a
defined extension which was of a composite type. Thanks to Bill de Beaubien for the pull request!
</action>
<action type="add" issue="44" dev="petromykhailysyn">
Remove unnessecary IOException from narrative generator API. Thanks to
Petro Mykhailysyn for the pull request!
</action>
<<<<<<< HEAD:src/changes/changes.xml
=======
<action type="add" issue="48" dev="wdebeau1">
Introduced a new
<![CDATA[<code>@ProvidesResources</code>]]> annotation which can be added to
resource provider and servers to allow them to declare additional resource
classes they are able to serve. This is useful if you have a server which can
serve up multiple classes for the same resource type (e.g. a server that sometimes
returns a default Patient, but sometimes uses a custom subclass).
Thanks to Bill de Beaubien for the pull request!
</action>
<action>
Add a new method <![CDATA[handleException]]> to the server interceptor
framework which allows interceptors to be notified of any exceptions and
runtime errors within server methods. Interceptors may optionally also
override the default error handling behaviour of the RestfulServer.
</action>
<action dev="wdebeau1">
Add constants to BaseResource for the "_id" search parameter which all resources
should support.
</action>
>>>>>>> d22a35788f57e9f7ce64bc8afc2ee7eaf29d94f2:src/changes/changes.xml
</release>
<release version="0.7" date="2014-Oct-23">
<action type="add" issue="30">
<![CDATA[<b>API CHANGE:</b>]]> The TagList class previously implemented ArrayList semantics,
but this has been replaced with LinkedHashMap semantics. This means that the list of
tags will no longer accept duplicate tags, but that tag order will still be
preserved. Thanks to Bill de Beaubien for reporting!
</action>
<action type="fix" issue="33">
Server was incorrectly including contained resources being returned as both contained resources, and as
top-level resources in the returned bundle for search operations.
Thanks to Bill de Beaubien for reporting! This also fixes Issue #20, thanks to
lephty for reporting!
</action>
<action type="add" dev="suranga">
Documentation fixes
</action>
<action type="add" dev="dougmartin">
Add a collection of new methods on the generic client which support the
<![CDATA[
<b><a href="./apidocs/ca/uhn/fhir/rest/client/IGenericClient.html#read(java.lang.Class,%20ca.uhn.fhir.model.primitive.UriDt)">read</a></b>,
<b><a href="./apidocs/ca/uhn/fhir/rest/client/IGenericClient.html#vread(java.lang.Class,%20ca.uhn.fhir.model.primitive.UriDt)">read</a></b>,
and <b><a href="./apidocs/ca/uhn/fhir/rest/client/IGenericClient.html#search(java.lang.Class,%20ca.uhn.fhir.model.primitive.UriDt)">search</a></b>
]]>
operations using an absolute URL. This allows developers to perform these operations using
URLs they obtained from other sources (or external resource references within resources). In
addition, the existing read/vread operations will now access absolute URL references if
they are passed in. Thanks to Doug Martin of the Regenstrief Center for Biomedical Informatics
for contributing this implementation!
</action>
<action type="fix">
Server implementation was not correctly figuring out its own FHIR Base URL when deployed
on Amazon Web Service server. Thanks to Jeffrey Ting and Bill De Beaubien of
Systems Made Simple for their help in figuring out this issue!
</action>
<action type="fix">
XML Parser failed to encode fields with both a resource reference child and
a primitive type child. Thanks to Jeffrey Ting and Bill De Beaubien of
Systems Made Simple for their help in figuring out this issue!
</action>
<action type="fix">
HAPI now runs successfully on Servlet 2.5 containers (such as Tomcat 6). Thanks to
Bernard Gitaadji for reporting and diagnosing the issue!
</action>
<action type="fix">
Summary (in the bundle entry) is now encoded by the XML and JSON parsers if supplied. Thanks to David Hay of
Orion Health for reporting this!
</action>
<action type="fix" issue="24">
Conformance profiles which are automatically generated by the server were missing a few mandatory elements,
which meant that the profile did not correctly validate. Thanks to Bill de Beaubien of Systems Made Simple
for reporting this!
</action>
<action type="fix">
XHTML (in narratives) containing escapable characters (e.g. &lt; or &quot;) will now always have those characters
escaped properly in encoded messages.
</action>
<action type="fix">
Resources containing entities which are not valid in basic XML (e.g. &amp;sect;) will have those
entities converted to their equivalent unicode characters when resources are encoded, since FHIR does
not allow extended entities in resource instances.
</action>
<action type="add">
Add a new client interceptor which adds HTTP Authorization Bearer Tokens (for use with OAUTH2 servers)
to client requests.
</action>
<action type="fix">
Add phloc-commons dependency explicitly, which resolves an issue building HAPI from source on
some platforms. Thanks to Odysseas Pentakalos for the patch!
</action>
<action type="add">
HAPI now logs a single line indicating the StAX implementation being used upon the
first time an XML parser is created.
</action>
<action type="fix">
Update methods on the server did not return a "content-location" header, but
only a "location" header. Both are required according to the FHIR specification.
Thanks to Bill de Beaubien of Systems Made Simple for reporting this!
</action>
<action type="fix" issue="26" dev="akley">
Parser failed to correctly read contained Binary resources. Thanks to Alexander Kley for
the patch!
</action>
<action type="fix" issue="29" dev="akley">
Calling encode multiple times on a resource with contained resources caused the contained
resources to be re-added (and the actual message to grow) with each encode pass. Thanks to
Alexander Kley for the test case!
</action>
<action type="fix">
JSON-encoded contained resources with the incorrect "_id" element (which should be "id", but some
incorrect examples exist on the FHIR specification) now parse correctly. In other words, HAPI
previously only accepted the correct "id" element, but now it also accepts the incorrect
"_id" element just to be more lenient.
</action>
<action type="fix">
Several unit tests failed on Windows (or any platform with non UTF-8 default encoding). This may
have also caused resource validation to fail occasionally on these platforms as well.
Thanks to Bill de Beaubien for reporting!
</action>
<action type="fix">
toString() method on TokenParam was incorrectly showing the system as the value.
Thanks to Bill de Beaubien for reporting!
</action>
<action type="update">
Documentation on contained resources contained a typo and did not actually produce contained resources. Thanks
to David Hay of Orion Health for reporting!
</action>
<action type="add" issue="31" dev="preston">
Add a
<![CDATA[<a href="https://www.vagrantup.com/">Vagrant</a>]]>
based environment (basically a fully built, self contained development environment) for
trying out the HAPI server modules. Thanks to Preston Lee for the pull request, and for
offering to maintain this!
</action>
<action type="add" issue="32" dev="jathman">
Change validation API so that it uses a return type instead of exceptions to communicate
validation failures. Thanks to Joe Athman for the pull request!
</action>
<action type="add" issue="35" dev="petromykhailysyn">
Add a client interceptor which adds an HTTP cookie to each client request. Thanks to
Petro Mykhailysyn for the pull request!
</action>
</release>
<release version="0.6" date="2014-Sep-08" description="This release brings a number of new features and bug fixes!">
<!--
<action type="add">
Allow generic client ... OAUTH
</action>
-->
<action type="add">
Add server interceptor framework, and new interceptor for logging incoming
requests.
</action>
<action type="add">
Add server validation framework for validating resources against the FHIR schemas and schematrons
</action>
<action type="fix">
Tester UI created double _format and _pretty param entries in searches. Thanks to Gered King of University
Health Network for reporting!
</action>
<action type="fix" issue="4">
Create method was incorrectly returning an HTTP 204 on sucessful completion, but
should be returning an HTTP 200 per the FHIR specification. Thanks to wanghaisheng
for reporting!
</action>
<action type="fix">
FHIR Tester UI now correctly sends UTF-8 charset in responses so that message payloads containing
non US-ASCII characters will correctly display in the browser
</action>
<action type="fix">
JSON parser was incorrectly encoding extensions on composite elements outside the element itself
(as is done correctly for non-composite elements) instead of inside of them. Thanks to David Hay of
Orion for reporting this!
</action>
<action type="add">
Contained/included resource instances received by a client are now automatically
added to any ResourceReferenceDt instancea in other resources which reference them.
</action>
<action type="add">
Add documentation on how to use eBay CORS Filter to support Cross Origin Resource
Sharing (CORS) to server. CORS support that was built in to the server itself has
been removed, as it did not work correctly (and was reinventing a wheel that others
have done a great job inventing). Thanks to Peter Bernhardt of Relay Health for all the assistance
in testing this!
</action>
<action type="fix">
IResource interface did not expose the getLanguage/setLanguage methods from BaseResource,
so the resource language was difficult to access.
</action>
<action type="fix">
JSON Parser now gives a more friendly error message if it tries to parse JSON with invalid use
of single quotes
</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 order to return status information
about the transaction processing.
</action>
<action type="add">
Update method in the server can now flag (via a field on the MethodOutcome object being returned)
that the result was actually a creation, and Create method can indicate that it was actually an
update. This has no effect other than to switch between the HTTP 200 and HTTP 201 status codes on the
response, but this may be useful in some circumstances.
</action>
<action type="fix">
Annotation client search methods with a specific resource type (e.g. List&lt;Patient&gt; search())
won't return any resources that aren't of the correct type that are received in a response
bundle (generally these are referenced resources, so they are populated in the reference fields instead).
Thanks to Tahura Chaudhry of University Health Network for the unit test!
</action>
<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>
<action type="fix">
Server search method for an unnamed query gets called if the client requests a named query
with the same parameter list. Thanks to Neal Acharya of University Health Network for reporting!
</action>
<action type="fix">
Category header (for tags) is correctly read in client for "read" operation
</action>
<action type="add">
Transaction method in server can now have parameter type Bundle instead of
List&lt;IResource&gt;
</action>
<action type="add">
HAPI parsers now use field access to get/set values instead of method accessors and mutators.
This should give a small performance boost.
</action>
<action type="fix">
JSON parser encodes resource references incorrectly, using the name "resource" instead
of the name "reference" for the actual reference. Thanks to
Ricky Nguyen for reporting and tracking down the issue!
</action>
<action type="fix">
Rename NotImpementedException to NotImplementedException (to correct typo)
</action>
<action type="fix">
Server setUseBrowserFriendlyContentType setting also respected for errors (e.g. OperationOutcome with 4xx/5xx status)
</action>
<action type="fix">
Fix performance issue in date/time datatypes where pattern matchers were not static
</action>
<action type="fix">
Server now gives a more helpful error message if a @Read method has a search parameter (which is invalid, but
previously lead to a very unhelpful error message). Thanks to Tahura Chaudhry of UHN for reporting!
</action>
<action type="fix">
Resource of type "List" failed to parse from a bundle correctly. Thanks to David Hay of Orion Health
for reporting!
</action>
<action type="fix">
QuantityParam correctly encodes approximate (~) prefix to values
</action>
<action type="fix" issue="14">
If a server defines a method with parameter "_id", incoming search requests for that method may
get delegated to the wrong method. Thanks to Neal Acharya for reporting!
</action>
<action type="add">
SecurityEvent.Object structural element has been renamed to
SecurityEvent.ObjectElement to avoid conflicting names with the
java Object class. Thanks to Laurie Macdougall-Sookraj of UHN for
reporting!
</action>
<action type="fix">
Text/narrative blocks that were created with a non-empty
namespace prefix (e.g. &lt;xhtml:div xmlns:xhtml="..."&gt;...&lt;/xhtml:div&gt;)
failed to encode correctly (prefix was missing in encoded resource)
</action>
<action type="fix">
Resource references previously encoded their children (display and reference)
in the wrong order so references with both would fail schema validation.
</action>
<action type="add">
SecurityEvent resource's enums now use friendly enum names instead of the unfriendly
numeric code values. Thanks to Laurie MacDougall-Sookraj of UHN for the
suggestion!
</action>
</release>
<release version="0.5" date="2014-Jul-30">
<action type="add">
HAPI has a number of RESTful method parameter types that have similar but not identical
purposes and confusing names. A cleanup has been undertaken to clean this up.
This means that a number of existing classes
have been deprocated in favour of new naming schemes.
<![CDATA[<br/><br/>]]>
All annotation-based clients and all server search method parameters are now named
(type)Param, for example: StringParam, TokenParam, etc.
<![CDATA[<br/><br/>]]>
All generic/fluent client method parameters are now named
(type)ClientParam, for example: StringClientParam, TokenClientParam, etc.
<![CDATA[<br/><br/>]]>
All renamed classes have been retained and deprocated, so this change should not cause any issues
for existing applications but those applications should be refactored to use the
new parameters when possible.
</action>
<action type="add">
Allow server methods to return wildcard generic types (e.g. List&lt;? extends IResource&gt;)
</action>
<action type="add">
Search parameters are not properly escaped and unescaped. E.g. for a token parameter such as
"&amp;identifier=system|codepart1\|codepart2"
</action>
<action type="add">
Add support for OPTIONS verb (which returns the server conformance statement)
</action>
<action type="add">
Add support for CORS headers in server
</action>
<action type="add">
Bump SLF4j dependency to latest version (1.7.7)
</action>
<action type="add">
Add interceptor framework for clients (annotation based and generic), and add interceptors
for configurable logging, capturing requests and responses, and HTTP basic auth.
</action>
<action type="fix">
Transaction client invocations with XML encoding were using the wrong content type ("application/xml+fhir" instead
of the correct "application/atom+xml"). Thanks to David Hay of Orion Health for surfacing this one!
</action>
<action type="add">
Bundle entries now support a link type of "search". Thanks to David Hay for the suggestion!
</action>
<action type="add" issue="1">
If a client receives a non 2xx response (e.g. HTTP 500) and the response body is a text/plain message or
an OperationOutcome resource, include the message in the exception message so that it will be
more conveniently displayed in logs and other places. Thanks to Neal Acharya for the suggestion!
</action>
<action type="add" issue="2">
Read invocations in the client now process the "Content-Location" header and use it to
populate the ID of the returned resource. Thanks to Neal Acharya for the suggestion!
</action>
<action type="fix" issue="3">
Fix issue where vread invocations on server incorrectly get routed to instance history method if one is
defined. Thanks to Neal Acharya from UHN for surfacing this one!
</action>
<action type="add">
Binary reads on a server not include the Content-Disposition header, to prevent HTML in binary
blobs from being used for nefarious purposes. See
<![CDATA[<a href="http://gforge.hl7.org/gf/project/fhir/tracker/?action=TrackerItemEdit&tracker_id=677&tracker_item_id=3298">FHIR Tracker Bug 3298</a>]]>
for more information.
</action>
<action type="add">
Support has been added for using an HTTP proxy for outgoing requests.
</action>
<action type="fix">
Fix: Primitive extensions declared against custom resource types
are encoded even if they have no value. Thanks to David Hay of Orion for
reporting this!
</action>
<action type="fix">
Fix: RESTful server deployed to a location where the URL to access it contained a
space (e.g. a WAR file with a space in the name) failed to work correctly.
Thanks to David Hay of Orion for reporting this!
</action>
</release>
<release version="0.4" date="2014-Jul-13">
<action type="add">
<![CDATA[<b>BREAKING CHANGE:</b>]]>: IdDt has been modified so that it
contains a partial or complete resource identity. Previously it contained
only the simple alphanumeric id of the resource (the part at the end of the "read" URL for
that resource) but it can now contain a complete URL or even a partial URL (e.g. "Patient/123")
and can optionally contain a version (e.g. "Patient/123/_history/456"). New methods have
been added to this datatype which provide just the numeric portion. See the JavaDoc
for more information.
</action>
<action type="add">
<![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
get/setElementSpecificId() methods. The old methods will be removed at some point. Resource
types are unchanged and retain their get/setId methods.
</action>
<action type="add">
Allow use of QuantityDt as a service parameter to support the "quantity" type. Previously
QuantityDt did not implement IQueryParameterType so it was not valid, and there was no way to
support quantity search parameters on the server (e.g. Observation.value-quantity)
</action>
<action type="add">
Introduce StringParameter type which can be used as a RESTful operation search parameter
type. StringParameter allows ":exact" matches to be specified in clients, and handled in servers.
</action>
<action type="add">
Parsers (XML/JSON) now support deleted entries in bundles
</action>
<action type="add">
Transaction method now supported in servers
</action>
<action type="add">
Support for Binary resources added (in servers, clients, parsers, etc.)
</action>
<action type="fix">
Support for Query resources fixed (in parser)
</action>
<action type="fix">
Nested contained resources (e.g. encoding a resource with a contained resource that itself contains a resource)
now parse and encode correctly, meaning that all contained resources are placed in the "contained" element
of the root resource, and the parser looks in the root resource for all container levels when stitching
contained resources back together.
</action>
<action type="fix">
Server methods with @Include parameter would sometimes fail when no _include was actually
specified in query strings.
</action>
<action type="fix">
Client requests for IdentifierDt types (such as Patient.identifier) did not create the correct
query string if the system is null.
</action>
<action type="add">
Add support for paging responses from RESTful servers.
</action>
<action type="fix">
Don't fail on narrative blocks in JSON resources with only an XML declaration but no content (these are
produced by the Health Intersections server)
</action>
<action type="fix">
Server now automatically compresses responses if the client indicates support
</action>
<action type="fix">
Server failed to support optional parameters when type is String and :exact qualifier is used
</action>
<action type="fix">
Read method in client correctly populated resource ID in returned object
</action>
<action type="add">
Support added for deleted-entry by/name, by/email, and comment from Tombstones spec
</action>
</release>
<release version="0.3" date="2014-May-12" description="This release corrects lots of bugs and introduces the fluent client mode">
</release>
</body>
</document>