Merge branch 'master' of github.com:jamesagnew/hapi-fhir

Conflicts:
	hapi-fhir-tutorial/simple-server/src/main/java/ca/uhn/fhir/example/ex3/Example03_PatientResourceProvider.java
	hapi-fhir-tutorial/simple-server/src/main/java/ca/uhn/fhir/example/ex3/Example03_SimpleRestfulServer.java
	src/changes/changes.xml
This commit is contained in:
James Agnew 2014-12-03 13:49:19 -05:00
commit 826fa1d7c1
25 changed files with 1007 additions and 136 deletions

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<classpath> <classpath>
<classpathentry kind="src" path="hapi-fhir-structures-dstu/target/generated-sources/tinder"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="output" path="bin"/> <classpathentry kind="output" path="bin"/>
</classpath> </classpath>

View File

@ -20,6 +20,9 @@ package ca.uhn.fhir.model.base.composite;
* #L% * #L%
*/ */
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
@ -99,6 +102,28 @@ public abstract class BaseHumanNameDt extends BaseIdentifiableElement {
public String getSuffixAsSingleString() { public String getSuffixAsSingleString() {
return ca.uhn.fhir.util.DatatypeUtil.joinStringsSpaceSeparated(getSuffix()); return ca.uhn.fhir.util.DatatypeUtil.joinStringsSpaceSeparated(getSuffix());
} }
/**
* Gets the value(s) for <b>text</b> (Text representation of the full name).
* creating it if it does
* not exist. Will not return <code>null</code>.
*
* <p>
* <b>Definition:</b>
* A full text representation of the name
* </p>
*/
public abstract StringDt getText() ;
/**
* Sets the value(s) for <b>text</b> (Text representation of the full name)
*
* <p>
* <b>Definition:</b>
* A full text representation of the name
* </p>
*/
public abstract BaseHumanNameDt setText(StringDt theValue);
@Override @Override
public String toString() { public String toString() {
@ -108,4 +133,14 @@ public abstract class BaseHumanNameDt extends BaseIdentifiableElement {
return b.toString(); return b.toString();
} }
public String getNameAsSingleString(){
List<StringDt> nameParts = new ArrayList<StringDt>();
nameParts.addAll(getPrefix());
nameParts.addAll(getGiven());
nameParts.addAll(getFamily());
nameParts.addAll(getSuffix());
if(nameParts.size() > 0) return ca.uhn.fhir.util.DatatypeUtil.joinStringsSpaceSeparated(nameParts);
else return getText().getValue();
}
} }

View File

@ -42,16 +42,11 @@ import ca.uhn.fhir.parser.DataFormatException;
public abstract class BaseDateTimeDt extends BasePrimitive<Date> { 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!! * Add any new formatters to the static block below!!
*/ */
private static final List<FastDateFormat> ourFormatters; 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 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 Pattern ourYearDashMonthPattern = Pattern.compile("[0-9]{4}-[0-9]{2}");
private static final FastDateFormat ourYearFormat = FastDateFormat.getInstance("yyyy"); private static final FastDateFormat ourYearFormat = FastDateFormat.getInstance("yyyy");
@ -67,8 +62,8 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
private static final FastDateFormat ourYearMonthFormat = FastDateFormat.getInstance("yyyy-MM"); private static final FastDateFormat ourYearMonthFormat = FastDateFormat.getInstance("yyyy-MM");
private static final FastDateFormat ourYearMonthNoDashesFormat = FastDateFormat.getInstance("yyyyMM"); 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 ourYearMonthPattern = Pattern.compile("[0-9]{4}[0-9]{2}");
private static final Pattern ourYearPattern = Pattern.compile("[0-9]{4}"); private static final Pattern ourYearPattern = Pattern.compile("[0-9]{4}");
static { static {
ArrayList<FastDateFormat> formatters = new ArrayList<FastDateFormat>(); ArrayList<FastDateFormat> formatters = new ArrayList<FastDateFormat>();
formatters.add(ourYearFormat); formatters.add(ourYearFormat);
@ -86,9 +81,51 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
} }
private TemporalPrecisionEnum myPrecision = TemporalPrecisionEnum.SECOND; private TemporalPrecisionEnum myPrecision = TemporalPrecisionEnum.SECOND;
private TimeZone myTimeZone;
private TimeZone myTimeZone;
private boolean myTimeZoneZulu = false; private boolean myTimeZoneZulu = false;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseDateTimeDt.class);
/**
* Constructor
*/
public BaseDateTimeDt() {
// nothing
}
/**
* Constructor
*
* @throws DataFormatException
* If the specified precision is not allowed for this type
*/
public BaseDateTimeDt(Date theDate, TemporalPrecisionEnum thePrecision) {
setValue(theDate, thePrecision);
if (isPrecisionAllowed(thePrecision) == false) {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + thePrecision + " precision): " + theDate);
}
}
/**
* Constructor
*
* @throws DataFormatException
* If the specified precision is not allowed for this type
*/
public BaseDateTimeDt(String theString) {
setValueAsString(theString);
if (isPrecisionAllowed(getPrecision()) == false) {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support " + getPrecision() + " precision): " + theString);
}
}
/**
* Constructor
*/
public BaseDateTimeDt(Date theDate, TemporalPrecisionEnum thePrecision, TimeZone theTimeZone) {
this(theDate, thePrecision);
setTimeZone(theTimeZone);
}
private void clearTimeZone() { private void clearTimeZone() {
myTimeZone = null; myTimeZone = null;
@ -136,6 +173,10 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
} }
} }
/**
* Returns the default precision for the given datatype
*/
protected abstract TemporalPrecisionEnum getDefaultPrecisionForDatatype();
/** /**
* Gets the precision for this datatype (using the default for the given type if not set) * Gets the precision for this datatype (using the default for the given type if not set)
@ -150,13 +191,8 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
} }
/** /**
* Returns the default precision for the given datatype * Returns the TimeZone associated with this dateTime's value. May return <code>null</code> if no timezone was
*/ * supplied.
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() { public TimeZone getTimeZone() {
return myTimeZone; return myTimeZone;
@ -204,57 +240,52 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
protected Date parse(String theValue) throws DataFormatException { protected Date parse(String theValue) throws DataFormatException {
try { try {
if (theValue.length() == 4 && ourYearPattern.matcher(theValue).matches()) { if (theValue.length() == 4 && ourYearPattern.matcher(theValue).matches()) {
if (isPrecisionAllowed(YEAR)) { if (!isPrecisionAllowed(YEAR)) {
setPrecision(YEAR); ourLog.debug("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support YEAR precision): " + theValue);
clearTimeZone();
return ((ourYearFormat).parse(theValue));
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support YEAR precision): " + theValue);
} }
setPrecision(YEAR);
clearTimeZone();
return ((ourYearFormat).parse(theValue));
} else if (theValue.length() == 6 && ourYearMonthPattern.matcher(theValue).matches()) { } else if (theValue.length() == 6 && ourYearMonthPattern.matcher(theValue).matches()) {
// Eg. 198401 (allow this just to be lenient) // Eg. 198401 (allow this just to be lenient)
if (isPrecisionAllowed(MONTH)) { if (!isPrecisionAllowed(MONTH)) {
setPrecision(MONTH); ourLog.debug("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
clearTimeZone();
return ((ourYearMonthNoDashesFormat).parse(theValue));
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
} }
setPrecision(MONTH);
clearTimeZone();
return ((ourYearMonthNoDashesFormat).parse(theValue));
} else if (theValue.length() == 7 && ourYearDashMonthPattern.matcher(theValue).matches()) { } else if (theValue.length() == 7 && ourYearDashMonthPattern.matcher(theValue).matches()) {
// E.g. 1984-01 (this is valid according to the spec) // E.g. 1984-01 (this is valid according to the spec)
if (isPrecisionAllowed(MONTH)) { if (!isPrecisionAllowed(MONTH)) {
setPrecision(MONTH); ourLog.debug("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support MONTH precision): " + theValue);
clearTimeZone();
return ((ourYearMonthFormat).parse(theValue));
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support MONTH precision): " + theValue);
} }
setPrecision(MONTH);
clearTimeZone();
return ((ourYearMonthFormat).parse(theValue));
} else if (theValue.length() == 8 && ourYearMonthDayPattern.matcher(theValue).matches()) { } else if (theValue.length() == 8 && ourYearMonthDayPattern.matcher(theValue).matches()) {
// Eg. 19840101 (allow this just to be lenient) // Eg. 19840101 (allow this just to be lenient)
if (isPrecisionAllowed(DAY)) { if (!isPrecisionAllowed(DAY)) {
setPrecision(DAY); ourLog.debug("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
clearTimeZone();
return ((ourYearMonthDayNoDashesFormat).parse(theValue));
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
} }
setPrecision(DAY);
clearTimeZone();
return ((ourYearMonthDayNoDashesFormat).parse(theValue));
} else if (theValue.length() == 10 && ourYearDashMonthDashDayPattern.matcher(theValue).matches()) { } else if (theValue.length() == 10 && ourYearDashMonthDashDayPattern.matcher(theValue).matches()) {
// E.g. 1984-01-01 (this is valid according to the spec) // E.g. 1984-01-01 (this is valid according to the spec)
if (isPrecisionAllowed(DAY)) { if (!isPrecisionAllowed(DAY)) {
setPrecision(DAY); ourLog.debug("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
clearTimeZone();
return ((ourYearMonthDayFormat).parse(theValue));
} else {
throw new DataFormatException("Invalid date/time string (datatype " + getClass().getSimpleName() + " does not support DAY precision): " + theValue);
} }
setPrecision(DAY);
clearTimeZone();
return ((ourYearMonthDayFormat).parse(theValue));
} else if (theValue.length() >= 18) { // date and time with possible time zone } else if (theValue.length() >= 18) { // date and time with possible time zone
int dotIndex = theValue.indexOf('.', 18); int dotIndex = theValue.indexOf('.', 18);
boolean hasMillis = dotIndex > -1; boolean hasMillis = dotIndex > -1;
if (!hasMillis && !isPrecisionAllowed(SECOND)) { if (!hasMillis && !isPrecisionAllowed(SECOND)) {
throw new DataFormatException("Invalid date/time string (data type does not support SECONDS precision): " + theValue); ourLog.debug("Invalid date/time string (data type does not support SECONDS precision): " + theValue);
} else if (hasMillis && !isPrecisionAllowed(MILLI)) { } else if (hasMillis && !isPrecisionAllowed(MILLI)) {
throw new DataFormatException("Invalid date/time string (data type " + getClass().getSimpleName() + " does not support MILLIS precision):" + theValue); ourLog.debug("Invalid date/time string (data type " + getClass().getSimpleName() + " does not support MILLIS precision):" + theValue);
} }
Date retVal; Date retVal;
@ -262,10 +293,11 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
try { try {
if (hasOffset(theValue)) { if (hasOffset(theValue)) {
retVal = ourYearMonthDayTimeMilliZoneFormat.parse(theValue); retVal = ourYearMonthDayTimeMilliZoneFormat.parse(theValue);
} else if (theValue.endsWith("Z")) } else if (theValue.endsWith("Z")) {
retVal = ourYearMonthDayTimeMilliUTCZFormat.parse(theValue); retVal = ourYearMonthDayTimeMilliUTCZFormat.parse(theValue);
else } else {
retVal = ourYearMonthDayTimeMilliFormat.parse(theValue); retVal = ourYearMonthDayTimeMilliFormat.parse(theValue);
}
} catch (ParseException p2) { } catch (ParseException p2) {
throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue); throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue);
} }
@ -316,8 +348,6 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
updateStringValue(); updateStringValue();
} }
private void setTimeZone(String theValueString, boolean hasMillis) { private void setTimeZone(String theValueString, boolean hasMillis) {
clearTimeZone(); clearTimeZone();
int timeZoneStart = 19; int timeZoneStart = 19;
@ -332,13 +362,11 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
} }
} }
public void setTimeZone(TimeZone theTimeZone) { public void setTimeZone(TimeZone theTimeZone) {
myTimeZone = theTimeZone; myTimeZone = theTimeZone;
updateStringValue(); updateStringValue();
} }
public void setTimeZoneZulu(boolean theTimeZoneZulu) { public void setTimeZoneZulu(boolean theTimeZoneZulu) {
myTimeZoneZulu = theTimeZoneZulu; myTimeZoneZulu = theTimeZoneZulu;
updateStringValue(); updateStringValue();
@ -353,9 +381,11 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
/** /**
* Sets the value of this date/time using the specified level of precision * Sets the value of this date/time using the specified level of precision
* *
* @param theValue The date value * @param theValue
* @param thePrecision The precision * The date value
* @throws DataFormatException * @param thePrecision
* The precision
* @throws DataFormatException
*/ */
public void setValue(Date theValue, TemporalPrecisionEnum thePrecision) throws DataFormatException { public void setValue(Date theValue, TemporalPrecisionEnum thePrecision) throws DataFormatException {
clearTimeZone(); clearTimeZone();
@ -369,4 +399,11 @@ public abstract class BaseDateTimeDt extends BasePrimitive<Date> {
super.setValueAsString(theValue); super.setValueAsString(theValue);
} }
/**
* For unit tests only
*/
static List<FastDateFormat> getFormatters() {
return ourFormatters;
}
} }

View File

@ -25,7 +25,16 @@ import java.util.Date;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.api.annotation.DatatypeDef; import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.SimpleSetter; import ca.uhn.fhir.model.api.annotation.SimpleSetter;
import ca.uhn.fhir.parser.DataFormatException;
/**
* Represents a FHIR date datatype. Valid precisions values for this type are:
* <ul>
* <li>{@link TemporalPrecisionEnum#YEAR}
* <li>{@link TemporalPrecisionEnum#MONTH}
* <li>{@link TemporalPrecisionEnum#DAY}
* </ul>
*/
@DatatypeDef(name = "date") @DatatypeDef(name = "date")
public class DateDt extends BaseDateTimeDt { public class DateDt extends BaseDateTimeDt {
@ -44,10 +53,9 @@ public class DateDt extends BaseDateTimeDt {
/** /**
* Constructor which accepts a date value and uses the {@link #DEFAULT_PRECISION} for this type * Constructor which accepts a date value and uses the {@link #DEFAULT_PRECISION} for this type
*/ */
@SimpleSetter(suffix="WithDayPrecision") @SimpleSetter(suffix = "WithDayPrecision")
public DateDt(@SimpleSetter.Parameter(name = "theDate") Date theDate) { public DateDt(@SimpleSetter.Parameter(name = "theDate") Date theDate) {
setValue(theDate); super(theDate, DEFAULT_PRECISION);
setPrecision(DEFAULT_PRECISION);
} }
/** /**
@ -57,11 +65,23 @@ public class DateDt extends BaseDateTimeDt {
* <li>{@link TemporalPrecisionEnum#MONTH} * <li>{@link TemporalPrecisionEnum#MONTH}
* <li>{@link TemporalPrecisionEnum#DAY} * <li>{@link TemporalPrecisionEnum#DAY}
* </ul> * </ul>
*
* @throws DataFormatException
* If the specified precision is not allowed for this type
*/ */
@SimpleSetter @SimpleSetter
public DateDt(@SimpleSetter.Parameter(name = "theDate") Date theDate, @SimpleSetter.Parameter(name = "thePrecision") TemporalPrecisionEnum thePrecision) { public DateDt(@SimpleSetter.Parameter(name = "theDate") Date theDate, @SimpleSetter.Parameter(name = "thePrecision") TemporalPrecisionEnum thePrecision) {
setValue(theDate); super(theDate, thePrecision);
setPrecision(thePrecision); }
/**
* Constructor which accepts a date as a string in FHIR format
*
* @throws DataFormatException
* If the precision in the date string is not allowed for this type
*/
public DateDt(String theDate) {
super(theDate);
} }
@Override @Override

View File

@ -26,7 +26,18 @@ import java.util.TimeZone;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.api.annotation.DatatypeDef; import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.SimpleSetter; import ca.uhn.fhir.model.api.annotation.SimpleSetter;
import ca.uhn.fhir.parser.DataFormatException;
/**
* Represents a FHIR dateTime datatype. Valid precisions values for this type are:
* <ul>
* <li>{@link TemporalPrecisionEnum#YEAR}
* <li>{@link TemporalPrecisionEnum#MONTH}
* <li>{@link TemporalPrecisionEnum#DAY}
* <li>{@link TemporalPrecisionEnum#SECOND}
* <li>{@link TemporalPrecisionEnum#MILLI}
* </ul>
*/
@DatatypeDef(name = "dateTime") @DatatypeDef(name = "dateTime")
public class DateTimeDt extends BaseDateTimeDt { public class DateTimeDt extends BaseDateTimeDt {
@ -43,18 +54,44 @@ public class DateTimeDt extends BaseDateTimeDt {
} }
/** /**
* Create a new DateTimeDt * Create a new DateTimeDt with seconds precision and the local time zone
*/ */
@SimpleSetter(suffix = "WithSecondsPrecision") @SimpleSetter(suffix = "WithSecondsPrecision")
public DateTimeDt(@SimpleSetter.Parameter(name = "theDate") Date theDate) { public DateTimeDt(@SimpleSetter.Parameter(name = "theDate") Date theDate) {
setValue(theDate); super(theDate, DEFAULT_PRECISION, TimeZone.getDefault());
setPrecision(DEFAULT_PRECISION);
setTimeZone(TimeZone.getDefault());
} }
/** /**
* Constructor which accepts a date value and a precision value. Valid * Constructor which accepts a date value and a precision value. Valid precisions values for this type are:
* precisions values for this type are: * <ul>
* <li>{@link TemporalPrecisionEnum#YEAR}
* <li>{@link TemporalPrecisionEnum#MONTH}
* <li>{@link TemporalPrecisionEnum#DAY}
* <li>{@link TemporalPrecisionEnum#SECOND}
* <li>{@link TemporalPrecisionEnum#MILLI}
* </ul>
*
* @throws DataFormatException
* If the specified precision is not allowed for this type
*/
@SimpleSetter
public DateTimeDt(@SimpleSetter.Parameter(name = "theDate") Date theDate, @SimpleSetter.Parameter(name = "thePrecision") TemporalPrecisionEnum thePrecision) {
super(theDate, thePrecision, TimeZone.getDefault());
}
/**
* Create a new instance using a string date/time
*
* @throws DataFormatException
* If the specified precision is not allowed for this type
*/
public DateTimeDt(String theValue) {
super(theValue);
}
/**
* Constructor which accepts a date value, precision value, and time zone. Valid precisions values for this type
* are:
* <ul> * <ul>
* <li>{@link TemporalPrecisionEnum#YEAR} * <li>{@link TemporalPrecisionEnum#YEAR}
* <li>{@link TemporalPrecisionEnum#MONTH} * <li>{@link TemporalPrecisionEnum#MONTH}
@ -63,18 +100,8 @@ public class DateTimeDt extends BaseDateTimeDt {
* <li>{@link TemporalPrecisionEnum#MILLI} * <li>{@link TemporalPrecisionEnum#MILLI}
* </ul> * </ul>
*/ */
@SimpleSetter public DateTimeDt(Date theDate, TemporalPrecisionEnum thePrecision, TimeZone theTimezone) {
public DateTimeDt(@SimpleSetter.Parameter(name = "theDate") Date theDate, @SimpleSetter.Parameter(name = "thePrecision") TemporalPrecisionEnum thePrecision) { super(theDate, thePrecision, theTimezone);
setValue(theDate);
setPrecision(thePrecision);
setTimeZone(TimeZone.getDefault());
}
/**
* Create a new instance using a string date/time
*/
public DateTimeDt(String theValue) {
setValueAsString(theValue);
} }
@Override @Override
@ -92,10 +119,11 @@ public class DateTimeDt extends BaseDateTimeDt {
} }
/** /**
* Returns a new instance of DateTimeDt with the current system time and SECOND precision * Returns a new instance of DateTimeDt with the current system time and SECOND precision and the system local time
* zone
*/ */
public static DateTimeDt withCurrentTime() { public static DateTimeDt withCurrentTime() {
return new DateTimeDt(new Date(), TemporalPrecisionEnum.SECOND); return new DateTimeDt(new Date(), TemporalPrecisionEnum.SECOND, TimeZone.getDefault());
} }
/** /**
@ -108,5 +136,4 @@ public class DateTimeDt extends BaseDateTimeDt {
return DEFAULT_PRECISION; return DEFAULT_PRECISION;
} }
} }

View File

@ -29,6 +29,13 @@ import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.SimpleSetter; import ca.uhn.fhir.model.api.annotation.SimpleSetter;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
/**
* Represents a FHIR instant datatype. Valid precisions values for this type are:
* <ul>
* <li>{@link TemporalPrecisionEnum#SECOND}
* <li>{@link TemporalPrecisionEnum#MILLI}
* </ul>
*/
@DatatypeDef(name = "instant") @DatatypeDef(name = "instant")
public class InstantDt extends BaseDateTimeDt { public class InstantDt extends BaseDateTimeDt {
@ -54,31 +61,39 @@ public class InstantDt extends BaseDateTimeDt {
* Create a new DateTimeDt * Create a new DateTimeDt
*/ */
public InstantDt(Calendar theCalendar) { public InstantDt(Calendar theCalendar) {
setValue(theCalendar.getTime()); super(theCalendar.getTime(), DEFAULT_PRECISION, theCalendar.getTimeZone());
setPrecision(DEFAULT_PRECISION);
setTimeZone(theCalendar.getTimeZone());
} }
/**
* Create a new instance using the given date, precision level, and time zone
*
* @throws DataFormatException
* If the specified precision is not allowed for this type
*/
public InstantDt(Date theDate, TemporalPrecisionEnum thePrecision, TimeZone theTimezone) {
super(theDate, thePrecision, theTimezone);
}
/** /**
* Create a new DateTimeDt using an existing value. <b>Use this constructor with caution</b>, * Create a new DateTimeDt using an existing value. <b>Use this constructor with caution</b>,
* as it may create more precision than warranted (since for example it is possible to pass in * as it may create more precision than warranted (since for example it is possible to pass in
* a DateTime with only a year, and this constructor will convert to an InstantDt with * a DateTime with only a year, and this constructor will convert to an InstantDt with
* milliseconds precision). * milliseconds precision).
*/ */
public InstantDt(BaseDateTimeDt theDateTime) { public InstantDt(BaseDateTimeDt theDateTime) {
// Do not call super(foo) here, we don't want to trigger a DataFormatException
setValue(theDateTime.getValue()); setValue(theDateTime.getValue());
setPrecision(DEFAULT_PRECISION); setPrecision(DEFAULT_PRECISION);
setTimeZone(theDateTime.getTimeZone()); setTimeZone(theDateTime.getTimeZone());
} }
/** /**
* Create a new DateTimeDt * Create a new DateTimeDt with the given date/time and {@link TemporalPrecisionEnum#MILLI} precision
*/ */
@SimpleSetter(suffix = "WithMillisPrecision") @SimpleSetter(suffix = "WithMillisPrecision")
public InstantDt(@SimpleSetter.Parameter(name = "theDate") Date theDate) { public InstantDt(@SimpleSetter.Parameter(name = "theDate") Date theDate) {
setValue(theDate); super(theDate, DEFAULT_PRECISION, TimeZone.getDefault());
setPrecision(DEFAULT_PRECISION);
setTimeZone(TimeZone.getDefault());
} }
/** /**
@ -105,7 +120,7 @@ public class InstantDt extends BaseDateTimeDt {
* @throws DataFormatException * @throws DataFormatException
*/ */
public InstantDt(String theString) { public InstantDt(String theString) {
setValueAsString(theString); super(theString);
} }
/** /**
@ -154,10 +169,10 @@ public class InstantDt extends BaseDateTimeDt {
/** /**
* Factory method which creates a new InstantDt with millisecond precision and initializes it with the * Factory method which creates a new InstantDt with millisecond precision and initializes it with the
* current time. * current time and the system local timezone.
*/ */
public static InstantDt withCurrentTime() { public static InstantDt withCurrentTime() {
return new InstantDt(new Date(), TemporalPrecisionEnum.MILLI); return new InstantDt(new Date(), TemporalPrecisionEnum.MILLI, TimeZone.getDefault());
} }
/** /**

View File

@ -51,7 +51,7 @@ public class PatientAuditor implements IResourceAuditor<Patient> {
@Override @Override
public String getName() { public String getName() {
if(myPatient != null){ if(myPatient != null){
return "Patient: " + myPatient.getNameFirstRep().getText().getValue(); return "Patient: " + myPatient.getNameFirstRep().getNameAsSingleString();
} }
return null; return null;
} }

View File

@ -300,9 +300,9 @@ public class AuditingInterceptor extends InterceptorAdapter {
} }
/** /**
* Create a particpant object based on information from the incoming servlet request * Create a participant object based on information from the incoming servlet request
* @param theServletRequest the incoming request * @param theServletRequest the incoming request
* @return a Particpant object populated with the user id, user name, and IP * @return a Participant object populated with the user id, user name, and IP
* @throws InvalidRequestException if auditing is required but userId is not specified as a request header * @throws InvalidRequestException if auditing is required but userId is not specified as a request header
* @throws NotImplementedException if the authorization type is OAuth * @throws NotImplementedException if the authorization type is OAuth
*/ */
@ -388,6 +388,7 @@ public class AuditingInterceptor extends InterceptorAdapter {
* @return the corresponding SecurityEventActionEnum (Read/View/Print, Create, Delete, etc) * @return the corresponding SecurityEventActionEnum (Read/View/Print, Create, Delete, etc)
*/ */
protected SecurityEventActionEnum mapResourceTypeToSecurityEventAction(RestfulOperationTypeEnum resourceOperationType) { protected SecurityEventActionEnum mapResourceTypeToSecurityEventAction(RestfulOperationTypeEnum resourceOperationType) {
if(resourceOperationType == null) return null;
switch (resourceOperationType) { switch (resourceOperationType) {
case READ: return SecurityEventActionEnum.READ_VIEW_PRINT; case READ: return SecurityEventActionEnum.READ_VIEW_PRINT;
case CREATE: return SecurityEventActionEnum.CREATE; case CREATE: return SecurityEventActionEnum.CREATE;
@ -409,6 +410,7 @@ public class AuditingInterceptor extends InterceptorAdapter {
* @return the corresponding SecurityEventObjectLifecycleEnum (Access/Use, Origination/Creation, Logical Deletion, etc) * @return the corresponding SecurityEventObjectLifecycleEnum (Access/Use, Origination/Creation, Logical Deletion, etc)
*/ */
protected SecurityEventObjectLifecycleEnum mapResourceTypeToSecurityLifecycle(RestfulOperationTypeEnum resourceOperationType) { protected SecurityEventObjectLifecycleEnum mapResourceTypeToSecurityLifecycle(RestfulOperationTypeEnum resourceOperationType) {
if(resourceOperationType == null) return null;
switch (resourceOperationType) { switch (resourceOperationType) {
case READ: return SecurityEventObjectLifecycleEnum.ACCESS_OR_USE; case READ: return SecurityEventObjectLifecycleEnum.ACCESS_OR_USE;
case CREATE: return SecurityEventObjectLifecycleEnum.ORIGINATION_OR_CREATION; case CREATE: return SecurityEventObjectLifecycleEnum.ORIGINATION_OR_CREATION;

View File

@ -1,58 +1,128 @@
package ca.uhn.fhir.model.primitive; package ca.uhn.fhir.model.primitive;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import static org.hamcrest.Matchers.*;
import ca.uhn.fhir.parser.DataFormatException; import static org.junit.Assert.*;
import org.apache.commons.lang3.time.FastDateFormat;
import org.junit.Before;
import org.junit.Test;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.TimeZone; import java.util.TimeZone;
import static org.junit.Assert.assertEquals; import org.apache.commons.lang3.time.FastDateFormat;
import static org.junit.Assert.assertNotNull; import org.hamcrest.Matchers;
import static org.junit.Assert.assertNull; import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.dstu.resource.Condition;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.validation.ValidationResult;
public class BaseDateTimeDtTest { public class BaseDateTimeDtTest {
private SimpleDateFormat myDateInstantParser; private SimpleDateFormat myDateInstantParser;
private FastDateFormat myDateInstantZoneParser; private FastDateFormat myDateInstantZoneParser;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseDateTimeDtTest.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseDateTimeDtTest.class);
private static FhirContext ourCtx = new FhirContext();
@Before @Before
public void before() { public void before() {
myDateInstantParser = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); 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")); myDateInstantZoneParser = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSSZ", TimeZone.getTimeZone("GMT-02:00"));
} }
@Test @Test
public void setTimezoneToZulu() { public void setTimezoneToZulu() {
DateTimeDt dt = new DateTimeDt(new Date(816411488000L)); DateTimeDt dt = new DateTimeDt(new Date(816411488000L));
// assertEquals("1995-11-14T23:58:08", dt.getValueAsString()); // assertEquals("1995-11-14T23:58:08", dt.getValueAsString());
dt.setTimeZoneZulu(true); dt.setTimeZoneZulu(true);
assertEquals("1995-11-15T04:58:08Z", dt.getValueAsString()); assertEquals("1995-11-15T04:58:08Z", dt.getValueAsString());
} }
@Test
public void testDateTimeInLocalTimezone() {
DateTimeDt dt = DateTimeDt.withCurrentTime();
String str = dt.getValueAsString();
char offset = str.charAt(19);
if (offset != '+' && offset != '-') {
fail("No timezone provided: " + str);
}
}
@Test
public void testInstantInLocalTimezone() {
InstantDt dt = InstantDt.withCurrentTime();
String str = dt.getValueAsString();
char offset = str.charAt(23);
if (offset != '+' && offset != '-') {
fail("No timezone provided: " + str);
}
}
/**
* Test for #57
*/
@Test
public void testDateParsesWithInvalidPrecision() {
Condition c = new Condition();
c.setDateAsserted(new DateDt());
c.getDateAsserted().setValueAsString("2001-01-02T11:13:33");
assertEquals(TemporalPrecisionEnum.SECOND, c.getDateAsserted().getPrecision());
String encoded = ourCtx.newXmlParser().encodeResourceToString(c);
Assert.assertThat(encoded, Matchers.containsString("value=\"2001-01-02T11:13:33\""));
c = ourCtx.newXmlParser().parseResource(Condition.class, encoded);
assertEquals("2001-01-02T11:13:33", c.getDateAsserted().getValueAsString());
assertEquals(TemporalPrecisionEnum.SECOND, c.getDateAsserted().getPrecision());
ValidationResult outcome = ourCtx.newValidator().validateWithResult(c);
String outcomeStr = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome.getOperationOutcome());
ourLog.info(outcomeStr);
assertThat(outcomeStr, containsString("date-primitive"));
}
/**
* Test for #57
*/
@Test
public void testConstructorRejectsInvalidPrecision() {
try {
new DateDt("2001-01-02T11:13:33");
fail();
} catch (DataFormatException e) {
assertThat(e.getMessage(), containsString("precision"));
}
try {
new InstantDt("2001-01-02");
fail();
} catch (DataFormatException e) {
assertThat(e.getMessage(), containsString("precision"));
}
}
@Test @Test
public void testFormats() throws Exception { public void testFormats() throws Exception {
Date instant = myDateInstantParser.parse("2001-02-03 13:01:02.555"); Date instant = myDateInstantParser.parse("2001-02-03 13:01:02.555");
for (FastDateFormat next : BaseDateTimeDt.getFormatters()) { for (FastDateFormat next : BaseDateTimeDt.getFormatters()) {
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
cal.setTime(instant); cal.setTime(instant);
String value = next.format(cal); String value = next.format(cal);
ourLog.info("String: {}", value); ourLog.info("String: {}", value);
DateTimeDt dt = new DateTimeDt(value); DateTimeDt dt = new DateTimeDt(value);
String reEncoded = next.format(dt.getValue()); String reEncoded = next.format(dt.getValue());
assertEquals(value, reEncoded); assertEquals(value, reEncoded);
} }
} }
@Test @Test
public void testParseDay() throws DataFormatException { public void testParseDay() throws DataFormatException {
DateTimeDt dt = new DateTimeDt(); DateTimeDt dt = new DateTimeDt();
@ -64,8 +134,7 @@ public class BaseDateTimeDtTest {
assertNull(dt.getTimeZone()); assertNull(dt.getTimeZone());
assertEquals(TemporalPrecisionEnum.DAY, dt.getPrecision()); assertEquals(TemporalPrecisionEnum.DAY, dt.getPrecision());
} }
@Test() @Test()
public void testParseMalformatted() throws DataFormatException { public void testParseMalformatted() throws DataFormatException {
DateTimeDt dt = new DateTimeDt("20120102"); DateTimeDt dt = new DateTimeDt("20120102");
@ -83,8 +152,8 @@ public class BaseDateTimeDtTest {
assertEquals(false, dt.isTimeZoneZulu()); assertEquals(false, dt.isTimeZoneZulu());
assertNull(dt.getTimeZone()); assertNull(dt.getTimeZone());
assertEquals(TemporalPrecisionEnum.MILLI, dt.getPrecision()); assertEquals(TemporalPrecisionEnum.MILLI, dt.getPrecision());
} }
@Test @Test
public void testParseMilliZone() throws DataFormatException { public void testParseMilliZone() throws DataFormatException {
InstantDt dt = new InstantDt(); InstantDt dt = new InstantDt();
@ -183,11 +252,12 @@ public class BaseDateTimeDtTest {
public void testSetValueByString() { public void testSetValueByString() {
InstantDt i = new InstantDt(); InstantDt i = new InstantDt();
i.setValueAsString("2014-06-20T20:22:09Z"); i.setValueAsString("2014-06-20T20:22:09Z");
assertNotNull(i.getValue()); assertNotNull(i.getValue());
assertNotNull(i.getValueAsString()); assertNotNull(i.getValueAsString());
assertEquals(1403295729000L, i.getValue().getTime()); assertEquals(1403295729000L, i.getValue().getTime());
assertEquals("2014-06-20T20:22:09Z",i.getValueAsString()); assertEquals("2014-06-20T20:22:09Z", i.getValueAsString());
} }
} }

View File

@ -0,0 +1,293 @@
package ca.uhn.fhir.rest.server.interceptor;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import sun.misc.BASE64Decoder;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import ca.uhn.fhir.model.base.resource.BaseSecurityEvent;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.resource.SecurityEvent;
import ca.uhn.fhir.model.dstu.resource.SecurityEvent.ObjectElement;
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventObjectLifecycleEnum;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventObjectTypeEnum;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventOutcomeEnum;
import ca.uhn.fhir.model.dstu.valueset.SecurityEventSourceTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.client.interceptor.UserInfoInterceptor;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.audit.IResourceAuditor;
import ca.uhn.fhir.rest.server.audit.PatientAuditor;
import ca.uhn.fhir.store.IAuditDataStore;
import ca.uhn.fhir.util.PortUtil;
public class AuditingInterceptorTest {
private static CloseableHttpClient ourClient;
private static int ourPort;
private static Server ourServer;
private static RestfulServer servlet;
private IServerInterceptor myInterceptor;
private class MockDataStore implements IAuditDataStore {
@Override
public void store(BaseSecurityEvent auditEvent) throws Exception {
//do nothing
}
}
@Test
public void testSinglePatient() throws Exception {
AuditingInterceptor interceptor = new AuditingInterceptor("HAPITEST", false);
Map<String, Class<? extends IResourceAuditor<? extends IResource>>> auditors = new HashMap<String, Class<? extends IResourceAuditor<? extends IResource>>>();
auditors.put("Patient", PatientAuditor.class);
interceptor.setAuditableResources(auditors );
servlet.setInterceptors(Collections.singletonList((IServerInterceptor)interceptor));
MockDataStore mockDataStore = mock(MockDataStore.class);
interceptor.setDataStore(mockDataStore);
String requestURL = "http://localhost:" + ourPort + "/Patient/1";
HttpGet httpGet = new HttpGet(requestURL);
httpGet.addHeader(UserInfoInterceptor.HEADER_USER_ID, "hapi-fhir-junit-user");
httpGet.addHeader(UserInfoInterceptor.HEADER_USER_NAME, "HAPI FHIR Junit Test Cases");
httpGet.addHeader(UserInfoInterceptor.HEADER_APPLICATION_NAME, "hapi-fhir-junit");
HttpResponse status = ourClient.execute(httpGet);
IOUtils.closeQuietly(status.getEntity().getContent());
ArgumentCaptor<SecurityEvent> captor = ArgumentCaptor.forClass(SecurityEvent.class);
verify(mockDataStore, times(1)).store(captor.capture());
SecurityEvent auditEvent = captor.getValue();
assertEquals(SecurityEventOutcomeEnum.SUCCESS.getCode(), auditEvent.getEvent().getOutcome().getValue());
assertEquals("HAPI FHIR Junit Test Cases", auditEvent.getParticipantFirstRep().getName().getValue());
assertEquals("hapi-fhir-junit-user", auditEvent.getParticipantFirstRep().getUserId().getValue());
assertEquals("hapi-fhir-junit", auditEvent.getSource().getIdentifier().getValue());
assertEquals("HAPITEST", auditEvent.getSource().getSite().getValue());
assertEquals(SecurityEventSourceTypeEnum.USER_DEVICE.getCode(), auditEvent.getSource().getTypeFirstRep().getCode().getValue());
List<ObjectElement> objects = auditEvent.getObject();
assertEquals(1, objects.size());
ObjectElement object = objects.get(0);
assertEquals("00001", object.getIdentifier().getValue().getValue());
assertEquals("Patient: PatientOne Test", object.getName().getValue());
assertEquals(SecurityEventObjectLifecycleEnum.ACCESS_OR_USE, object.getLifecycle().getValueAsEnum());
assertEquals(SecurityEventObjectTypeEnum.PERSON, object.getType().getValueAsEnum());
assertEquals(requestURL, new String(new BASE64Decoder().decodeBuffer(object.getQuery().getValueAsString())));
}
@Test
public void testBundle() throws Exception {
AuditingInterceptor interceptor = new AuditingInterceptor("HAPITEST", false);
Map<String, Class<? extends IResourceAuditor<? extends IResource>>> auditors = new HashMap<String, Class<? extends IResourceAuditor<? extends IResource>>>();
auditors.put("Patient", PatientAuditor.class);
interceptor.setAuditableResources(auditors );
servlet.setInterceptors(Collections.singletonList((IServerInterceptor)interceptor));
MockDataStore mockDataStore = mock(MockDataStore.class);
interceptor.setDataStore(mockDataStore);
String requestURL = "http://localhost:" + ourPort + "/Patient?_id=1,2";
HttpGet httpGet = new HttpGet(requestURL);
httpGet.addHeader(UserInfoInterceptor.HEADER_USER_ID, "hapi-fhir-junit-user");
httpGet.addHeader(UserInfoInterceptor.HEADER_USER_NAME, "HAPI FHIR Junit Test Cases");
httpGet.addHeader(UserInfoInterceptor.HEADER_APPLICATION_NAME, "hapi-fhir-junit");
HttpResponse status = ourClient.execute(httpGet);
IOUtils.closeQuietly(status.getEntity().getContent());
ArgumentCaptor<SecurityEvent> captor = ArgumentCaptor.forClass(SecurityEvent.class);
verify(mockDataStore, times(1)).store(captor.capture());
SecurityEvent auditEvent = captor.getValue();
assertEquals(SecurityEventOutcomeEnum.SUCCESS.getCode(), auditEvent.getEvent().getOutcome().getValue());
assertEquals("HAPI FHIR Junit Test Cases", auditEvent.getParticipantFirstRep().getName().getValue());
assertEquals("hapi-fhir-junit-user", auditEvent.getParticipantFirstRep().getUserId().getValue());
assertEquals("hapi-fhir-junit", auditEvent.getSource().getIdentifier().getValue());
assertEquals("HAPITEST", auditEvent.getSource().getSite().getValue());
assertEquals(SecurityEventSourceTypeEnum.USER_DEVICE.getCode(), auditEvent.getSource().getTypeFirstRep().getCode().getValue());
List<ObjectElement> objects = auditEvent.getObject();
assertEquals(2, objects.size());
for(ObjectElement object: objects){
if("00001".equals(object.getIdentifier().getValue().getValue())){
assertEquals("Patient: PatientOne Test", object.getName().getValue());
}else if("00002".equals(object.getIdentifier().getValue().getValue())){
assertEquals("Patient: Ms Laura Elizabeth MacDougall Sookraj B.Sc.", object.getName().getValue());
}else{
fail("Unexpected patient identifier being audited: " + object.getIdentifier().getValue().getValue());
}
assertEquals(requestURL, new String(new BASE64Decoder().decodeBuffer(object.getQuery().getValueAsString())));
assertEquals(SecurityEventObjectTypeEnum.PERSON, object.getType().getValueAsEnum());
}
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
}
@Before
public void before() {
myInterceptor = mock(IServerInterceptor.class);
servlet.setInterceptors(Collections.singletonList(myInterceptor));
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
ServletHandler proxyHandler = new ServletHandler();
servlet = new RestfulServer();
servlet.setResourceProviders(patientProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
public static class DummyPatientResourceProvider implements IResourceProvider {
public Map<String, Patient> getIdToPatient() {
Map<String, Patient> idToPatient = new HashMap<String, Patient>();
{
Patient patient = createPatient1();
idToPatient.put("1", patient);
}
{
Patient patient = new Patient();
patient.getIdentifier().add(new IdentifierDt());
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
patient.getIdentifier().get(0).setValue("00002");
HumanNameDt name = new HumanNameDt();
name.addPrefix("Ms");
name.addGiven("Laura");
name.addGiven("Elizabeth");
name.addFamily("MacDougall");
name.addFamily("Sookraj");
name.addSuffix("B.Sc.");
patient.getName().add(name);
patient.getGender().setText("F");
patient.getId().setValue("2");
idToPatient.put("2", patient);
}
return idToPatient;
}
/**
* Retrieve the resource by its identifier
*
* @param theId
* The resource identity
* @return The resource
*/
@Read()
public Patient getResourceById(@IdParam IdDt theId) {
String key = theId.getIdPart();
Patient retVal = getIdToPatient().get(key);
return retVal;
}
/**
* Retrieve the resource by its identifier
*
* @param theId
* The resource identity
* @return The resource
*/
@Search()
public List<Patient> getResourceById(@RequiredParam(name = "_id") TokenOrListParam theIds) {
List<Patient> patients = new ArrayList<Patient>();
for(BaseCodingDt id: theIds.getListAsCodings()){
Patient patient = getIdToPatient().get(id.getCodeElement().getValue());
if (patient != null) {
patients.add(patient);
}
}
return patients;
}
@Override
public Class<Patient> getResourceType() {
return Patient.class;
}
private Patient createPatient1() {
Patient patient = new Patient();
patient.addIdentifier();
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
patient.getIdentifier().get(0).setValue("00001");
patient.addName();
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientOne");
patient.getGender().setText("M");
patient.getId().setValue("1");
return patient;
}
}
}

View File

@ -14,7 +14,7 @@
<groupId>ca.uhn.hapi.example</groupId> <groupId>ca.uhn.hapi.example</groupId>
<artifactId>hapi-fhir-example-simple-server</artifactId> <artifactId>hapi-fhir-example-simple-server</artifactId>
<version>0.8-SNAPSHOT</version> <version>0.7</version>
<packaging>war</packaging> <packaging>war</packaging>
<name>HAPI FHIR Example - Simple Server</name> <name>HAPI FHIR Example - Simple Server</name>
@ -35,14 +35,20 @@
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId> <artifactId>hapi-fhir-base</artifactId>
<version>0.8-SNAPSHOT</version> <version>0.7</version>
</dependency> </dependency>
<!-- At least one "structures" JAR must also be included -->
<!--
Beginning in HAPI 0.8, at least one "structures" JAR must also be included.
For now it is commented out.
-->
<!--
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu</artifactId> <artifactId>hapi-fhir-structures-dstu</artifactId>
<version>0.8-SNAPSHOT</version> <version>0.8-SNAPSHOT</version>
</dependency> </dependency>
-->
<!-- <!--
HAPI-FHIR uses Logback for logging support. The logback library is included HAPI-FHIR uses Logback for logging support. The logback library is included

View File

@ -0,0 +1 @@
curl -H "Content-Type: application/xml+fhir" -X POST -d '<Patient xmlns="http://hl7.org/fhir"><name><family value="Fireman"/><given value="John"/></name></Patient>' "http://localhost:8080/example03/Patient"

View File

@ -10,10 +10,13 @@ import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.Read; import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
@ -24,14 +27,17 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
public class Example03_PatientResourceProvider implements IResourceProvider { public class Example03_PatientResourceProvider implements IResourceProvider {
private Map<Long, Patient> myPatients = new HashMap<Long, Patient>(); private Map<Long, Patient> myPatients = new HashMap<Long, Patient>();
private long myNextId = 1; private Long myNextId = 1L;
/** Constructor */ /** Constructor */
public Example03_PatientResourceProvider() { public Example03_PatientResourceProvider() {
Long id = myNextId++;
Patient pat1 = new Patient(); Patient pat1 = new Patient();
pat1.setId(new IdDt(id));
pat1.addIdentifier().setSystem("http://acme.com/MRNs").setValue("7000135"); pat1.addIdentifier().setSystem("http://acme.com/MRNs").setValue("7000135");
pat1.addName().addFamily("Simpson").addGiven("Homer").addGiven("J"); pat1.addName().addFamily("Simpson").addGiven("Homer").addGiven("J");
myPatients.put(myNextId++, pat1); myPatients.put(id, pat1);
} }
/** All Resource Providers must implement this method */ /** All Resource Providers must implement this method */
@ -49,11 +55,10 @@ public class Example03_PatientResourceProvider implements IResourceProvider {
} }
return retVal; return retVal;
} }
/** Simple implementation of the "create" method */ /** Create/save a new resource */
@Create @Create
public MethodOutcome create(@ResourceParam Patient thePatient) { public MethodOutcome create(@ResourceParam Patient thePatient) {
// Give the resource the next sequential ID // Give the resource the next sequential ID
long id = myNextId++; long id = myNextId++;
thePatient.setId(new IdDt(id)); thePatient.setId(new IdDt(id));
@ -62,11 +67,10 @@ public class Example03_PatientResourceProvider implements IResourceProvider {
myPatients.put(id, thePatient); myPatients.put(id, thePatient);
// Inform the server of the ID for the newly stored resource // Inform the server of the ID for the newly stored resource
return new MethodOutcome(thePatient.getId()); return new MethodOutcome(thePatient.getId());
} }
/** FHIR "Search" operation for Patient resources */ /** Simple "search" implementation **/
@Search @Search
public List<Patient> search() { public List<Patient> search() {
List<Patient> retVal = new ArrayList<Patient>(); List<Patient> retVal = new ArrayList<Patient>();
@ -74,5 +78,21 @@ public class Example03_PatientResourceProvider implements IResourceProvider {
return retVal; return retVal;
} }
/** A search with a parameter */
@Search
public List<Patient> search(@RequiredParam(name="family") StringParam theParam) {
List<Patient> retVal = new ArrayList<Patient>();
// Loop through the patients looking for matches
for (Patient next : myPatients.values()) {
String familyName = next.getNameFirstRep().getFamilyAsSingleString().toLowerCase();
if (familyName.contains(theParam.getValue().toLowerCase()) == false) {
continue;
}
retVal.add(next);
}
return retVal;
}
} }

View File

@ -0,0 +1,75 @@
package ca.uhn.fhir.example.ex4;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
/**
* This is the most basic resource provider, showing only a single
* read method on a resource provider
*/
public class Example04_PatientResourceProvider implements IResourceProvider {
private Map<Long, Patient> myPatients = new HashMap<Long, Patient>();
private Long myNextId = 1L;
/** Constructor */
public Example04_PatientResourceProvider() {
Patient pat1 = new Patient();
pat1.addIdentifier().setSystem("http://acme.com/MRNs").setValue("7000135");
pat1.addName().addFamily("Simpson").addGiven("Homer").addGiven("J");
myPatients.put(myNextId++, pat1);
}
/** All Resource Providers must implement this method */
@Override
public Class<? extends IResource> getResourceType() {
return Patient.class;
}
/** Simple implementation of the "read" method */
@Read()
public Patient read(@IdParam IdDt theId) {
Patient retVal = myPatients.get(theId.getIdPartAsLong());
if (retVal == null) {
throw new ResourceNotFoundException(theId);
}
return retVal;
}
/** Create/save a new resource */
@Create
public MethodOutcome create(@ResourceParam Patient thePatient) {
// Give the resource the next sequential ID
long id = myNextId++;
thePatient.setId(new IdDt(id));
// Store the resource in memory
myPatients.put(id, thePatient);
// Inform the server of the ID for the newly stored resource
return new MethodOutcome(thePatient.getId());
}
/** Simple "search" implementation **/
@Search
public List<Patient> search() {
List<Patient> retVal = new ArrayList<Patient>();
retVal.addAll(myPatients.values());
return retVal;
}
}

View File

@ -0,0 +1,19 @@
package ca.uhn.fhir.example.ex4;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import ca.uhn.fhir.rest.server.RestfulServer;
@WebServlet("/example04/*")
public class Example04_SimpleRestfulServer extends RestfulServer {
private static final long serialVersionUID = 1L;
@Override
protected void initialize() throws ServletException {
setResourceProviders(new Example04_PatientResourceProvider());
}
}

View File

@ -13,7 +13,7 @@
<groupId>ca.uhn.hapi.example</groupId> <groupId>ca.uhn.hapi.example</groupId>
<artifactId>hapi-fhir-example-skeleton-project</artifactId> <artifactId>hapi-fhir-example-skeleton-project</artifactId>
<version>0.8-SNAPSHOT</version> <version>0.7</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>HAPI FHIR Example - Skeleton Project</name> <name>HAPI FHIR Example - Skeleton Project</name>
@ -34,15 +34,20 @@
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId> <artifactId>hapi-fhir-base</artifactId>
<version>0.8-SNAPSHOT</version> <version>0.7</version>
</dependency> </dependency>
<!-- At least one "structures" JAR must also be included --> <!--
Beginning in HAPI 0.8, at least one "structures" JAR must also be included.
For now it is commented out.
-->
<!--
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu</artifactId> <artifactId>hapi-fhir-structures-dstu</artifactId>
<version>0.8-SNAPSHOT</version> <version>0.8-SNAPSHOT</version>
</dependency> </dependency>
-->
<!-- <!--
HAPI-FHIR uses Logback for logging support. The logback library is included 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 automatically by Maven as a part of the hapi-fhir-base dependency, but you
@ -55,6 +60,31 @@
<version>1.1.2</version> <version>1.1.2</version>
</dependency> </dependency>
<!--
The following two dependencies are required only if you are using
Schematron resource validation. Otherwise, they may be omitted.
-->
<dependency>
<groupId>com.phloc</groupId>
<artifactId>phloc-schematron</artifactId>
<version>2.7.1</version>
</dependency>
<dependency>
<groupId>com.phloc</groupId>
<artifactId>phloc-commons</artifactId>
<version>4.3.3</version>
</dependency>
<!--
The following dependency is only required for narrative generator
support. If you are not using this, it may be removed.
-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -19,13 +19,13 @@ public class Example07_ClientSearch {
Patient patient = client.read(Patient.class, "4529"); Patient patient = client.read(Patient.class, "4529");
// Print the ID of the newly created resource // Print the ID of the newly created resource
System.out.println(patient.getId()); System.out.println("Found ID: " + patient.getId());
// Change the gender and send an update to the server // Change the gender and send an update to the server
patient.setGender(AdministrativeGenderCodesEnum.F); patient.setGender(AdministrativeGenderCodesEnum.F);
MethodOutcome outcome = client.update().resource(patient).execute(); MethodOutcome outcome = client.update().resource(patient).execute();
System.out.println(outcome.getId()); System.out.println("Now have ID: " + outcome.getId());
} }
} }

View File

@ -0,0 +1,34 @@
package ca.uhn.fhir.example;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu.resource.Encounter;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
public class Example08_ValidateResource {
public static void main(String[] args) {
// Create an encounter with an invalid status and no class
Encounter enc = new Encounter();
enc.getStatus().setValueAsString("invalid_status");
// Create a new validator
FhirContext ctx = new FhirContext();
FhirValidator validator = ctx.newValidator();
// Did we succeed?
ValidationResult result = validator.validateWithResult(enc);
System.out.println("Success: " + result.isSuccessful());
// What was the result
OperationOutcome outcome = result.getOperationOutcome();
IParser parser = ctx.newXmlParser().setPrettyPrint(true);
System.out.println(parser.encodeResourceToString(outcome));
}
}

View File

@ -0,0 +1,30 @@
package ca.uhn.fhir.example;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu.resource.Encounter;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
public class Example09_NarrativeGenerator {
public static void main(String[] args) {
// Create an encounter with an invalid status and no class
Patient pat = new Patient();
pat.addName().addFamily("Simpson").addGiven("Homer").addGiven("Jay");
pat.addAddress().addLine("342 Evergreen Terrace").addLine("Springfield");
pat.addIdentifier().setLabel("MRN: 12345");
// Create a new context and enable the narrative generator
FhirContext ctx = new FhirContext();
ctx.setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
String res = ctx.newJsonParser().setPrettyPrint(true).encodeResourceToString(pat);
System.out.println(res);
}
}

View File

@ -0,0 +1,24 @@
package ca.uhn.fhir.example;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.CodeDt;
import ca.uhn.fhir.parser.IParser;
public class Example10_Extensions {
public static void main(String[] args) {
Patient pat = new Patient();
pat.addName().addFamily("Simpson").addGiven("Homer");
String url = "http://acme.org#eyeColour";
boolean isModifier = false;
pat.addUndeclaredExtension(isModifier, url).setValue(new CodeDt("blue"));;
IParser p = new FhirContext().newXmlParser().setPrettyPrint(true);
String encoded = p.encodeResourceToString(pat);
System.out.println(encoded);
}
}

View File

@ -0,0 +1,27 @@
package ca.uhn.fhir.example;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Extension;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.CodeDt;
@ResourceDef(name="Patient")
public class Example11_ExtendedPatient extends Patient {
@Child(name = "eyeColour")
@Extension(url="http://acme.org/#extpt", definedLocally = false, isModifier = false)
private CodeDt myEyeColour;
public CodeDt getEyeColour() {
if (myEyeColour == null) {
myEyeColour = new CodeDt();
}
return myEyeColour;
}
public void setEyeColour(CodeDt theEyeColour) {
myEyeColour = theEyeColour;
}
}

View File

@ -0,0 +1,22 @@
package ca.uhn.fhir.example;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.primitive.CodeDt;
import ca.uhn.fhir.parser.IParser;
public class Example11_UseExtensions {
public static void main(String[] args) {
Example11_ExtendedPatient pat = new Example11_ExtendedPatient();
pat.addName().addFamily("Simpson").addGiven("Homer");
pat.setEyeColour(new CodeDt("blue"));
IParser p = new FhirContext().newXmlParser().setPrettyPrint(true);
String encoded = p.encodeResourceToString(pat);
System.out.println(encoded);
}
}

23
pom.xml
View File

@ -593,6 +593,29 @@
<id>ROOT</id> <id>ROOT</id>
<modules> <modules>
</modules> </modules>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>${maven_assembly_plugin_version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<attach>false</attach>
<descriptors>
<descriptor>${project.basedir}/src/assembly/hapi-fhir-sample-projects.xml</descriptor>
<!-- <descriptor>src/assembly/hapi-jdk14.xml</descriptor> -->
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile> </profile>
<profile> <profile>
<id>SIGN_ARTIFACTS</id> <id>SIGN_ARTIFACTS</id>

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>sample-projects</id>
<formats>
<format>zip</format>
<format>tar.bz2</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>${project.basedir}/hapi-fhir-tutorial</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>simple-server/**</include>
<include>skeleton-project/**</include>
</includes>
<excludes>
<exclude>*/target/**</exclude>
<exclude>*/.classpath</exclude>
<exclude>*/.project</exclude>
<exclude>*/.settings/**</exclude>
</excludes>
</fileSet>
<!--
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory>/lib</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
-->
</fileSets>
<!--
<dependencySets>
<dependencySet>
<outputDirectory>/lib/dependency</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<unpack>false</unpack>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
-->
</assembly>

View File

@ -156,6 +156,15 @@
were applied to other client instances for the same client interface as well. (Issue were applied to other client instances for the same client interface as well. (Issue
did not affect generic/fluent clients) did not affect generic/fluent clients)
</action> </action>
<action type="fix" issue="57">
DateDt, DateTimeDt and types InstantDt types now do not throw an exception
if they are used to parse a value with the wrong level of precision for
the given type but do throw an exception if the wrong level of precision
is passed into their constructors.<![CDATA[<br/><br/>]]>
This means that HAPI FHIR can now successfully parse resources from external
sources that have the wrong level of precision, but will generate a validation
error if the resource is validated. Thanks to Alexander Kley for the suggestion!
</action>
<action type="fix"> <action type="fix">
Encoding a Binary resource without a content type set should not result in a NullPointerException. Thanks Encoding a Binary resource without a content type set should not result in a NullPointerException. Thanks
to Alexander Kley for reporting! to Alexander Kley for reporting!