diff --git a/.classpath b/.classpath index 8778ddd58a3..d617da7776f 100644 --- a/.classpath +++ b/.classpath @@ -1,5 +1,6 @@ + diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/composite/BaseHumanNameDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/composite/BaseHumanNameDt.java index 42172d44ec1..41fd7dad8a6 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/composite/BaseHumanNameDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/base/composite/BaseHumanNameDt.java @@ -20,6 +20,9 @@ package ca.uhn.fhir.model.base.composite; * #L% */ +import java.util.ArrayList; +import java.util.List; + import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -99,6 +102,28 @@ public abstract class BaseHumanNameDt extends BaseIdentifiableElement { public String getSuffixAsSingleString() { return ca.uhn.fhir.util.DatatypeUtil.joinStringsSpaceSeparated(getSuffix()); } + + /** + * Gets the value(s) for text (Text representation of the full name). + * creating it if it does + * not exist. Will not return null. + * + *

+ * Definition: + * A full text representation of the name + *

+ */ + public abstract StringDt getText() ; + + /** + * Sets the value(s) for text (Text representation of the full name) + * + *

+ * Definition: + * A full text representation of the name + *

+ */ + public abstract BaseHumanNameDt setText(StringDt theValue); @Override public String toString() { @@ -108,4 +133,14 @@ public abstract class BaseHumanNameDt extends BaseIdentifiableElement { return b.toString(); } + public String getNameAsSingleString(){ + List nameParts = new ArrayList(); + 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(); + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BaseDateTimeDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BaseDateTimeDt.java index 75a46273d27..498876c028e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BaseDateTimeDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BaseDateTimeDt.java @@ -42,16 +42,11 @@ import ca.uhn.fhir.parser.DataFormatException; public abstract class BaseDateTimeDt extends BasePrimitive { - /** - * For unit tests only - */ - static List getFormatters() { - return ourFormatters; - } /* * Add any new formatters to the static block below!! */ private static final List 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"); @@ -67,8 +62,8 @@ public abstract class BaseDateTimeDt extends BasePrimitive { 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 formatters = new ArrayList(); formatters.add(ourYearFormat); @@ -86,9 +81,51 @@ public abstract class BaseDateTimeDt extends BasePrimitive { } private TemporalPrecisionEnum myPrecision = TemporalPrecisionEnum.SECOND; - private TimeZone myTimeZone; + private TimeZone myTimeZone; 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() { myTimeZone = null; @@ -136,6 +173,10 @@ public abstract class BaseDateTimeDt extends BasePrimitive { } } + /** + * 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) @@ -150,13 +191,8 @@ public abstract class BaseDateTimeDt extends BasePrimitive { } /** - * Returns the default precision for the given datatype - */ - protected abstract TemporalPrecisionEnum getDefaultPrecisionForDatatype(); - - /** - * Returns the TimeZone associated with this dateTime's value. May return - * null if no timezone was supplied. + * Returns the TimeZone associated with this dateTime's value. May return null if no timezone was + * supplied. */ public TimeZone getTimeZone() { return myTimeZone; @@ -204,57 +240,52 @@ public abstract class BaseDateTimeDt extends BasePrimitive { 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); + if (!isPrecisionAllowed(YEAR)) { + ourLog.debug("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()) { // 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); + if (!isPrecisionAllowed(MONTH)) { + ourLog.debug("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()) { // 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); + if (!isPrecisionAllowed(MONTH)) { + ourLog.debug("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()) { // 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); + if (!isPrecisionAllowed(DAY)) { + ourLog.debug("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()) { // 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); + if (!isPrecisionAllowed(DAY)) { + ourLog.debug("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 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); + ourLog.debug("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); + ourLog.debug("Invalid date/time string (data type " + getClass().getSimpleName() + " does not support MILLIS precision):" + theValue); } Date retVal; @@ -262,10 +293,11 @@ public abstract class BaseDateTimeDt extends BasePrimitive { try { if (hasOffset(theValue)) { retVal = ourYearMonthDayTimeMilliZoneFormat.parse(theValue); - } else if (theValue.endsWith("Z")) + } else if (theValue.endsWith("Z")) { retVal = ourYearMonthDayTimeMilliUTCZFormat.parse(theValue); - else + } else { retVal = ourYearMonthDayTimeMilliFormat.parse(theValue); + } } catch (ParseException p2) { throw new DataFormatException("Invalid data/time string (" + p2.getMessage() + "): " + theValue); } @@ -316,8 +348,6 @@ public abstract class BaseDateTimeDt extends BasePrimitive { updateStringValue(); } - - private void setTimeZone(String theValueString, boolean hasMillis) { clearTimeZone(); int timeZoneStart = 19; @@ -332,13 +362,11 @@ public abstract class BaseDateTimeDt extends BasePrimitive { } } - public void setTimeZone(TimeZone theTimeZone) { myTimeZone = theTimeZone; updateStringValue(); } - public void setTimeZoneZulu(boolean theTimeZoneZulu) { myTimeZoneZulu = theTimeZoneZulu; updateStringValue(); @@ -353,9 +381,11 @@ public abstract class BaseDateTimeDt extends BasePrimitive { /** * Sets the value of this date/time using the specified level of precision * - * @param theValue The date value - * @param thePrecision The precision - * @throws DataFormatException + * @param theValue + * The date value + * @param thePrecision + * The precision + * @throws DataFormatException */ public void setValue(Date theValue, TemporalPrecisionEnum thePrecision) throws DataFormatException { clearTimeZone(); @@ -369,4 +399,11 @@ public abstract class BaseDateTimeDt extends BasePrimitive { super.setValueAsString(theValue); } + /** + * For unit tests only + */ + static List getFormatters() { + return ourFormatters; + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/DateDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/DateDt.java index bfb20323432..f8bd7e6b298 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/DateDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/DateDt.java @@ -25,7 +25,16 @@ import java.util.Date; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.api.annotation.DatatypeDef; 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: + *
    + *
  • {@link TemporalPrecisionEnum#YEAR} + *
  • {@link TemporalPrecisionEnum#MONTH} + *
  • {@link TemporalPrecisionEnum#DAY} + *
+ */ @DatatypeDef(name = "date") 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 */ - @SimpleSetter(suffix="WithDayPrecision") + @SimpleSetter(suffix = "WithDayPrecision") public DateDt(@SimpleSetter.Parameter(name = "theDate") Date theDate) { - setValue(theDate); - setPrecision(DEFAULT_PRECISION); + super(theDate, DEFAULT_PRECISION); } /** @@ -57,11 +65,23 @@ public class DateDt extends BaseDateTimeDt { *
  • {@link TemporalPrecisionEnum#MONTH} *
  • {@link TemporalPrecisionEnum#DAY} * + * + * @throws DataFormatException + * If the specified precision is not allowed for this type */ @SimpleSetter public DateDt(@SimpleSetter.Parameter(name = "theDate") Date theDate, @SimpleSetter.Parameter(name = "thePrecision") TemporalPrecisionEnum thePrecision) { - setValue(theDate); - setPrecision(thePrecision); + super(theDate, 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 diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/DateTimeDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/DateTimeDt.java index efe7c57f018..14a86ae75d3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/DateTimeDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/DateTimeDt.java @@ -26,7 +26,18 @@ import java.util.TimeZone; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.api.annotation.DatatypeDef; 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: + *
      + *
    • {@link TemporalPrecisionEnum#YEAR} + *
    • {@link TemporalPrecisionEnum#MONTH} + *
    • {@link TemporalPrecisionEnum#DAY} + *
    • {@link TemporalPrecisionEnum#SECOND} + *
    • {@link TemporalPrecisionEnum#MILLI} + *
    + */ @DatatypeDef(name = "dateTime") 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") public DateTimeDt(@SimpleSetter.Parameter(name = "theDate") Date theDate) { - setValue(theDate); - setPrecision(DEFAULT_PRECISION); - setTimeZone(TimeZone.getDefault()); + super(theDate, DEFAULT_PRECISION, TimeZone.getDefault()); } /** - * Constructor which accepts a date value and a precision value. Valid - * precisions values for this type are: + * Constructor which accepts a date value and a precision value. Valid precisions values for this type are: + *
      + *
    • {@link TemporalPrecisionEnum#YEAR} + *
    • {@link TemporalPrecisionEnum#MONTH} + *
    • {@link TemporalPrecisionEnum#DAY} + *
    • {@link TemporalPrecisionEnum#SECOND} + *
    • {@link TemporalPrecisionEnum#MILLI} + *
    + * + * @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: *
      *
    • {@link TemporalPrecisionEnum#YEAR} *
    • {@link TemporalPrecisionEnum#MONTH} @@ -63,18 +100,8 @@ public class DateTimeDt extends BaseDateTimeDt { *
    • {@link TemporalPrecisionEnum#MILLI} *
    */ - @SimpleSetter - public DateTimeDt(@SimpleSetter.Parameter(name = "theDate") Date theDate, @SimpleSetter.Parameter(name = "thePrecision") TemporalPrecisionEnum thePrecision) { - setValue(theDate); - setPrecision(thePrecision); - setTimeZone(TimeZone.getDefault()); - } - - /** - * Create a new instance using a string date/time - */ - public DateTimeDt(String theValue) { - setValueAsString(theValue); + public DateTimeDt(Date theDate, TemporalPrecisionEnum thePrecision, TimeZone theTimezone) { + super(theDate, thePrecision, theTimezone); } @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() { - 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; } - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/InstantDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/InstantDt.java index 388196f1e4a..df9026c93b3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/InstantDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/InstantDt.java @@ -29,6 +29,13 @@ import ca.uhn.fhir.model.api.annotation.DatatypeDef; import ca.uhn.fhir.model.api.annotation.SimpleSetter; import ca.uhn.fhir.parser.DataFormatException; +/** + * Represents a FHIR instant datatype. Valid precisions values for this type are: + *
      + *
    • {@link TemporalPrecisionEnum#SECOND} + *
    • {@link TemporalPrecisionEnum#MILLI} + *
    + */ @DatatypeDef(name = "instant") public class InstantDt extends BaseDateTimeDt { @@ -54,31 +61,39 @@ public class InstantDt extends BaseDateTimeDt { * Create a new DateTimeDt */ public InstantDt(Calendar theCalendar) { - setValue(theCalendar.getTime()); - setPrecision(DEFAULT_PRECISION); - setTimeZone(theCalendar.getTimeZone()); + super(theCalendar.getTime(), DEFAULT_PRECISION, 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. Use this constructor with caution, * 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 - * milliseconds precision). + * milliseconds precision). */ public InstantDt(BaseDateTimeDt theDateTime) { + // Do not call super(foo) here, we don't want to trigger a DataFormatException setValue(theDateTime.getValue()); setPrecision(DEFAULT_PRECISION); setTimeZone(theDateTime.getTimeZone()); } /** - * Create a new DateTimeDt + * Create a new DateTimeDt with the given date/time and {@link TemporalPrecisionEnum#MILLI} precision */ @SimpleSetter(suffix = "WithMillisPrecision") public InstantDt(@SimpleSetter.Parameter(name = "theDate") Date theDate) { - setValue(theDate); - setPrecision(DEFAULT_PRECISION); - setTimeZone(TimeZone.getDefault()); + super(theDate, DEFAULT_PRECISION, TimeZone.getDefault()); } /** @@ -105,7 +120,7 @@ public class InstantDt extends BaseDateTimeDt { * @throws DataFormatException */ 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 - * current time. + * current time and the system local timezone. */ public static InstantDt withCurrentTime() { - return new InstantDt(new Date(), TemporalPrecisionEnum.MILLI); + return new InstantDt(new Date(), TemporalPrecisionEnum.MILLI, TimeZone.getDefault()); } /** diff --git a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/audit/PatientAuditor.java b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/audit/PatientAuditor.java index ea7d0fc1f34..e35509ec085 100644 --- a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/audit/PatientAuditor.java +++ b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/audit/PatientAuditor.java @@ -51,7 +51,7 @@ public class PatientAuditor implements IResourceAuditor { @Override public String getName() { if(myPatient != null){ - return "Patient: " + myPatient.getNameFirstRep().getText().getValue(); + return "Patient: " + myPatient.getNameFirstRep().getNameAsSingleString(); } return null; } diff --git a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/interceptor/AuditingInterceptor.java b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/interceptor/AuditingInterceptor.java index 564d2b498f8..c6920f48c70 100644 --- a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/interceptor/AuditingInterceptor.java +++ b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/interceptor/AuditingInterceptor.java @@ -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 - * @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 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) */ protected SecurityEventActionEnum mapResourceTypeToSecurityEventAction(RestfulOperationTypeEnum resourceOperationType) { + if(resourceOperationType == null) return null; switch (resourceOperationType) { case READ: return SecurityEventActionEnum.READ_VIEW_PRINT; 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) */ protected SecurityEventObjectLifecycleEnum mapResourceTypeToSecurityLifecycle(RestfulOperationTypeEnum resourceOperationType) { + if(resourceOperationType == null) return null; switch (resourceOperationType) { case READ: return SecurityEventObjectLifecycleEnum.ACCESS_OR_USE; case CREATE: return SecurityEventObjectLifecycleEnum.ORIGINATION_OR_CREATION; diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/BaseDateTimeDtTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/BaseDateTimeDtTest.java index ab2b62d4493..517f0f9880d 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/BaseDateTimeDtTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/model/primitive/BaseDateTimeDtTest.java @@ -1,58 +1,128 @@ package ca.uhn.fhir.model.primitive; -import ca.uhn.fhir.model.api.TemporalPrecisionEnum; -import ca.uhn.fhir.parser.DataFormatException; -import org.apache.commons.lang3.time.FastDateFormat; -import org.junit.Before; -import org.junit.Test; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import org.apache.commons.lang3.time.FastDateFormat; +import org.hamcrest.Matchers; +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 { private SimpleDateFormat myDateInstantParser; private FastDateFormat myDateInstantZoneParser; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseDateTimeDtTest.class); - + + private static FhirContext ourCtx = new FhirContext(); + @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()); + // assertEquals("1995-11-14T23:58:08", dt.getValueAsString()); dt.setTimeZoneZulu(true); 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 public void testFormats() throws Exception { Date instant = myDateInstantParser.parse("2001-02-03 13:01:02.555"); for (FastDateFormat next : BaseDateTimeDt.getFormatters()) { - + Calendar cal = Calendar.getInstance(); 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); } } - + @Test public void testParseDay() throws DataFormatException { DateTimeDt dt = new DateTimeDt(); @@ -64,8 +134,7 @@ public class BaseDateTimeDtTest { assertNull(dt.getTimeZone()); assertEquals(TemporalPrecisionEnum.DAY, dt.getPrecision()); } - - + @Test() public void testParseMalformatted() throws DataFormatException { DateTimeDt dt = new DateTimeDt("20120102"); @@ -83,8 +152,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(); @@ -183,11 +252,12 @@ public class BaseDateTimeDtTest { 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()); + assertEquals("2014-06-20T20:22:09Z", i.getValueAsString()); } + } diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuditingInterceptorTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuditingInterceptorTest.java new file mode 100644 index 00000000000..d4161181c4d --- /dev/null +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/interceptor/AuditingInterceptorTest.java @@ -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>> auditors = new HashMap>>(); + 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 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 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>> auditors = new HashMap>>(); + 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 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 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 getIdToPatient() { + Map idToPatient = new HashMap(); + { + 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 getResourceById(@RequiredParam(name = "_id") TokenOrListParam theIds) { + List patients = new ArrayList(); + for(BaseCodingDt id: theIds.getListAsCodings()){ + Patient patient = getIdToPatient().get(id.getCodeElement().getValue()); + if (patient != null) { + patients.add(patient); + } + } + return patients; + } + + + @Override + public Class 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; + } + + } + +} diff --git a/hapi-fhir-tutorial/simple-server/pom.xml b/hapi-fhir-tutorial/simple-server/pom.xml index f449283803c..34036d1c1f1 100644 --- a/hapi-fhir-tutorial/simple-server/pom.xml +++ b/hapi-fhir-tutorial/simple-server/pom.xml @@ -14,7 +14,7 @@ ca.uhn.hapi.example hapi-fhir-example-simple-server - 0.8-SNAPSHOT + 0.7 war HAPI FHIR Example - Simple Server @@ -35,14 +35,20 @@ ca.uhn.hapi.fhir hapi-fhir-base - 0.8-SNAPSHOT + 0.7 - + + + + + + + + com.phloc + phloc-schematron + 2.7.1 + + + com.phloc + phloc-commons + 4.3.3 + + + + + org.thymeleaf + thymeleaf + 2.1.3.RELEASE + + diff --git a/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example07_ClientSearch.java b/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example07_ClientSearch.java index be4a01db439..217849b0b06 100644 --- a/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example07_ClientSearch.java +++ b/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example07_ClientSearch.java @@ -19,13 +19,13 @@ public class Example07_ClientSearch { Patient patient = client.read(Patient.class, "4529"); // 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 patient.setGender(AdministrativeGenderCodesEnum.F); MethodOutcome outcome = client.update().resource(patient).execute(); - System.out.println(outcome.getId()); + System.out.println("Now have ID: " + outcome.getId()); } } diff --git a/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example08_ValidateResource.java b/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example08_ValidateResource.java new file mode 100644 index 00000000000..01a0963b7bc --- /dev/null +++ b/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example08_ValidateResource.java @@ -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)); + + + } + +} diff --git a/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example09_NarrativeGenerator.java b/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example09_NarrativeGenerator.java new file mode 100644 index 00000000000..07545899995 --- /dev/null +++ b/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example09_NarrativeGenerator.java @@ -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); + } + +} diff --git a/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example10_Extensions.java b/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example10_Extensions.java new file mode 100644 index 00000000000..c8cc39bd1b4 --- /dev/null +++ b/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example10_Extensions.java @@ -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); + } + +} diff --git a/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example11_ExtendedPatient.java b/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example11_ExtendedPatient.java new file mode 100644 index 00000000000..74c18fe4196 --- /dev/null +++ b/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example11_ExtendedPatient.java @@ -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; + } + +} diff --git a/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example11_UseExtensions.java b/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example11_UseExtensions.java new file mode 100644 index 00000000000..a69f7c539a2 --- /dev/null +++ b/hapi-fhir-tutorial/skeleton-project/src/main/java/ca/uhn/fhir/example/Example11_UseExtensions.java @@ -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); + + } + +} diff --git a/pom.xml b/pom.xml index 1630bde883f..c58ee41af83 100644 --- a/pom.xml +++ b/pom.xml @@ -593,6 +593,29 @@ ROOT + + + + maven-assembly-plugin + ${maven_assembly_plugin_version} + + + package + + single + + + false + + ${project.basedir}/src/assembly/hapi-fhir-sample-projects.xml + + + + + + + + SIGN_ARTIFACTS diff --git a/src/assembly/hapi-fhir-sample-projects.xml b/src/assembly/hapi-fhir-sample-projects.xml new file mode 100644 index 00000000000..cf37384068b --- /dev/null +++ b/src/assembly/hapi-fhir-sample-projects.xml @@ -0,0 +1,51 @@ + + + + sample-projects + + + zip + tar.bz2 + + + false + + + + ${project.basedir}/hapi-fhir-tutorial + / + + simple-server/** + skeleton-project/** + + + */target/** + */.classpath + */.project + */.settings/** + + + + + + + + + diff --git a/src/changes/changes.xml b/src/changes/changes.xml index ed46c3b6fb0..b5f0a0bdf53 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -156,6 +156,15 @@ were applied to other client instances for the same client interface as well. (Issue did not affect generic/fluent clients) + + 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.
    ]]> + 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! +
    Encoding a Binary resource without a content type set should not result in a NullPointerException. Thanks to Alexander Kley for reporting!