Improvements to client and SP indexing in JPA server
This commit is contained in:
parent
519590ee27
commit
23083a9283
|
@ -20,16 +20,23 @@ package ca.uhn.fhir.rest.gclient;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
public interface IHistoryTyped<T> extends IClientExecutable<IHistoryTyped<T>, T> {
|
||||
import java.util.Date;
|
||||
|
||||
public interface IHistoryTyped<T> extends IClientExecutable<IHistoryTyped<T>, T> {
|
||||
|
||||
/**
|
||||
* Request that the server return only resource versions that were created at or after the given time (inclusive)
|
||||
* Request that the server return only the history elements between the
|
||||
* specific range
|
||||
*/
|
||||
IHistoryTyped<T> since(Date theCutoff);
|
||||
IHistoryTyped<T> at(DateRangeParam theDateRangeParam);
|
||||
|
||||
/**
|
||||
* Request that the server return only up to <code>theCount</code> number of resources
|
||||
*/
|
||||
IHistoryTyped<T> count(Integer theCount);
|
||||
|
||||
/**
|
||||
* Request that the server return only resource versions that were created at or after the given time (inclusive)
|
||||
|
@ -41,9 +48,9 @@ public interface IHistoryTyped<T> extends IClientExecutable<IHistoryTyped<T>, T>
|
|||
IHistoryTyped<T> since(IPrimitiveType<Date> theCutoff);
|
||||
|
||||
/**
|
||||
* Request that the server return only up to <code>theCount</code> number of resources
|
||||
* Request that the server return only resource versions that were created at or after the given time (inclusive)
|
||||
*/
|
||||
IHistoryTyped<T> count(Integer theCount);
|
||||
IHistoryTyped<T> since(Date theCutoff);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
package ca.uhn.fhir.rest.param;
|
||||
|
||||
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.EQUAL;
|
||||
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.GREATERTHAN_OR_EQUALS;
|
||||
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.LESSTHAN_OR_EQUALS;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterAnd;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.api.QualifiedParamList;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static ca.uhn.fhir.rest.param.ParamPrefixEnum.*;
|
||||
import static java.lang.String.format;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
|
@ -25,15 +35,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import java.util.*;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterAnd;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.api.QualifiedParamList;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
||||
|
||||
|
@ -53,14 +54,12 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
/**
|
||||
* Constructor which takes two Dates representing the lower and upper bounds of the range (inclusive on both ends)
|
||||
*
|
||||
* @param theLowerBound
|
||||
* A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound
|
||||
* A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
*/
|
||||
public DateRangeParam(Date theLowerBound, Date theUpperBound) {
|
||||
this();
|
||||
|
@ -84,22 +83,22 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
setRangeFromDatesInclusive(theDateParam.getValueAsString(), theDateParam.getValueAsString());
|
||||
} else {
|
||||
switch (theDateParam.getPrefix()) {
|
||||
case EQUAL:
|
||||
setRangeFromDatesInclusive(theDateParam.getValueAsString(), theDateParam.getValueAsString());
|
||||
break;
|
||||
case STARTS_AFTER:
|
||||
case GREATERTHAN:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
validateAndSet(theDateParam, null);
|
||||
break;
|
||||
case ENDS_BEFORE:
|
||||
case LESSTHAN:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
validateAndSet(null, theDateParam);
|
||||
break;
|
||||
default:
|
||||
// Should not happen
|
||||
throw new InvalidRequestException("Invalid comparator for date range parameter:" + theDateParam.getPrefix() + ". This is a bug.");
|
||||
case EQUAL:
|
||||
setRangeFromDatesInclusive(theDateParam.getValueAsString(), theDateParam.getValueAsString());
|
||||
break;
|
||||
case STARTS_AFTER:
|
||||
case GREATERTHAN:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
validateAndSet(theDateParam, null);
|
||||
break;
|
||||
case ENDS_BEFORE:
|
||||
case LESSTHAN:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
validateAndSet(null, theDateParam);
|
||||
break;
|
||||
default:
|
||||
// Should not happen
|
||||
throw new InvalidRequestException("Invalid comparator for date range parameter:" + theDateParam.getPrefix() + ". This is a bug.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -107,14 +106,12 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
/**
|
||||
* Constructor which takes two Dates representing the lower and upper bounds of the range (inclusive on both ends)
|
||||
*
|
||||
* @param theLowerBound
|
||||
* A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound
|
||||
* A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
*/
|
||||
public DateRangeParam(DateParam theLowerBound, DateParam theUpperBound) {
|
||||
this();
|
||||
|
@ -124,14 +121,12 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
/**
|
||||
* Constructor which takes two Dates representing the lower and upper bounds of the range (inclusive on both ends)
|
||||
*
|
||||
* @param theLowerBound
|
||||
* A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound
|
||||
* A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
*/
|
||||
public DateRangeParam(IPrimitiveType<Date> theLowerBound, IPrimitiveType<Date> theUpperBound) {
|
||||
this();
|
||||
|
@ -141,14 +136,12 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
/**
|
||||
* Constructor which takes two strings representing the lower and upper bounds of the range (inclusive on both ends)
|
||||
*
|
||||
* @param theLowerBound
|
||||
* An unqualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Either theLowerBound or theUpperBound may both be populated, or
|
||||
* one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound
|
||||
* An unqualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Either theLowerBound or theUpperBound may both be populated, or
|
||||
* one may be null, but it is not valid for both to be null.
|
||||
* @param theLowerBound An unqualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Either theLowerBound or theUpperBound may both be populated, or
|
||||
* one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound An unqualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Either theLowerBound or theUpperBound may both be populated, or
|
||||
* one may be null, but it is not valid for both to be null.
|
||||
*/
|
||||
public DateRangeParam(String theLowerBound, String theUpperBound) {
|
||||
this();
|
||||
|
@ -172,31 +165,49 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
} else {
|
||||
|
||||
switch (theParsed.getPrefix()) {
|
||||
case GREATERTHAN:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
if (myLowerBound != null) {
|
||||
throw new InvalidRequestException("Can not have multiple date range parameters for the same param that specify a lower bound");
|
||||
}
|
||||
myLowerBound = theParsed;
|
||||
break;
|
||||
case LESSTHAN:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
if (myUpperBound != null) {
|
||||
throw new InvalidRequestException("Can not have multiple date range parameters for the same param that specify an upper bound");
|
||||
}
|
||||
myUpperBound = theParsed;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidRequestException("Unknown comparator: " + theParsed.getPrefix());
|
||||
case GREATERTHAN:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
if (myLowerBound != null) {
|
||||
throw new InvalidRequestException("Can not have multiple date range parameters for the same param that specify a lower bound");
|
||||
}
|
||||
myLowerBound = theParsed;
|
||||
break;
|
||||
case LESSTHAN:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
if (myUpperBound != null) {
|
||||
throw new InvalidRequestException("Can not have multiple date range parameters for the same param that specify an upper bound");
|
||||
}
|
||||
myUpperBound = theParsed;
|
||||
break;
|
||||
default:
|
||||
throw new InvalidRequestException("Unknown comparator: " + theParsed.getPrefix());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof DateRangeParam)) {
|
||||
return false;
|
||||
}
|
||||
DateRangeParam other = (DateRangeParam) obj;
|
||||
return Objects.equals(myLowerBound, other.myLowerBound) &&
|
||||
Objects.equals(myUpperBound, other.myUpperBound);
|
||||
}
|
||||
|
||||
public DateParam getLowerBound() {
|
||||
return myLowerBound;
|
||||
}
|
||||
|
||||
public DateRangeParam setLowerBound(DateParam theLowerBound) {
|
||||
validateAndSet(theLowerBound, myUpperBound);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getLowerBoundAsInstant() {
|
||||
if (myLowerBound == null) {
|
||||
return null;
|
||||
|
@ -204,19 +215,19 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
Date retVal = myLowerBound.getValue();
|
||||
if (myLowerBound.getPrefix() != null) {
|
||||
switch (myLowerBound.getPrefix()) {
|
||||
case GREATERTHAN:
|
||||
case STARTS_AFTER:
|
||||
retVal = myLowerBound.getPrecision().add(retVal, 1);
|
||||
break;
|
||||
case EQUAL:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
break;
|
||||
case LESSTHAN:
|
||||
case APPROXIMATE:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
case ENDS_BEFORE:
|
||||
case NOT_EQUAL:
|
||||
throw new IllegalStateException("Unvalid lower bound comparator: " + myLowerBound.getPrefix());
|
||||
case GREATERTHAN:
|
||||
case STARTS_AFTER:
|
||||
retVal = myLowerBound.getPrecision().add(retVal, 1);
|
||||
break;
|
||||
case EQUAL:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
break;
|
||||
case LESSTHAN:
|
||||
case APPROXIMATE:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
case ENDS_BEFORE:
|
||||
case NOT_EQUAL:
|
||||
throw new IllegalStateException("Unvalid lower bound comparator: " + myLowerBound.getPrefix());
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
|
@ -226,6 +237,11 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
return myUpperBound;
|
||||
}
|
||||
|
||||
public DateRangeParam setUpperBound(DateParam theUpperBound) {
|
||||
validateAndSet(myLowerBound, theUpperBound);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getUpperBoundAsInstant() {
|
||||
if (myUpperBound == null) {
|
||||
return null;
|
||||
|
@ -233,21 +249,21 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
Date retVal = myUpperBound.getValue();
|
||||
if (myUpperBound.getPrefix() != null) {
|
||||
switch (myUpperBound.getPrefix()) {
|
||||
case LESSTHAN:
|
||||
case ENDS_BEFORE:
|
||||
retVal = new Date(retVal.getTime() - 1L);
|
||||
break;
|
||||
case EQUAL:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
retVal = myUpperBound.getPrecision().add(retVal, 1);
|
||||
retVal = new Date(retVal.getTime() - 1L);
|
||||
break;
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
case GREATERTHAN:
|
||||
case APPROXIMATE:
|
||||
case NOT_EQUAL:
|
||||
case STARTS_AFTER:
|
||||
throw new IllegalStateException("Unvalid upper bound comparator: " + myUpperBound.getPrefix());
|
||||
case LESSTHAN:
|
||||
case ENDS_BEFORE:
|
||||
retVal = new Date(retVal.getTime() - 1L);
|
||||
break;
|
||||
case EQUAL:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
retVal = myUpperBound.getPrecision().add(retVal, 1);
|
||||
retVal = new Date(retVal.getTime() - 1L);
|
||||
break;
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
case GREATERTHAN:
|
||||
case APPROXIMATE:
|
||||
case NOT_EQUAL:
|
||||
case STARTS_AFTER:
|
||||
throw new IllegalStateException("Unvalid upper bound comparator: " + myUpperBound.getPrefix());
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
|
@ -273,46 +289,55 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
return bound != null && !bound.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(myLowerBound, myUpperBound);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return (getLowerBoundAsInstant() == null) && (getUpperBoundAsInstant() == null);
|
||||
}
|
||||
|
||||
public DateRangeParam setLowerBound(DateParam theLowerBound) {
|
||||
validateAndSet(theLowerBound, myUpperBound);
|
||||
/**
|
||||
* Sets the lower bound using a string that is compliant with
|
||||
* FHIR dateTime format (ISO-8601).
|
||||
* <p>
|
||||
* This lower bound is assumed to have a <code>ge</code>
|
||||
* (greater than or equals) modifier.
|
||||
* </p>
|
||||
*/
|
||||
public DateRangeParam setLowerBound(String theLowerBound) {
|
||||
setLowerBound(new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the range from a pair of dates, inclusive on both ends
|
||||
*
|
||||
* @param theLowerBound
|
||||
* A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound
|
||||
* A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
*/
|
||||
public void setRangeFromDatesInclusive(Date theLowerBound, Date theUpperBound) {
|
||||
DateParam lowerBound = theLowerBound != null
|
||||
? new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound) : null;
|
||||
? new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound) : null;
|
||||
DateParam upperBound = theUpperBound != null
|
||||
? new DateParam(LESSTHAN_OR_EQUALS, theUpperBound) : null;
|
||||
? new DateParam(LESSTHAN_OR_EQUALS, theUpperBound) : null;
|
||||
validateAndSet(lowerBound, upperBound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the range from a pair of dates, inclusive on both ends
|
||||
*
|
||||
* @param theLowerBound
|
||||
* A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound
|
||||
* A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
*/
|
||||
public void setRangeFromDatesInclusive(DateParam theLowerBound, DateParam theUpperBound) {
|
||||
validateAndSet(theLowerBound, theUpperBound);
|
||||
|
@ -323,14 +348,12 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
* theLowerBound is after theUpperBound, thie method will automatically reverse
|
||||
* the order of the arguments in order to create an inclusive range.
|
||||
*
|
||||
* @param theLowerBound
|
||||
* A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound
|
||||
* A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
*/
|
||||
public void setRangeFromDatesInclusive(IPrimitiveType<Date> theLowerBound, IPrimitiveType<Date> theUpperBound) {
|
||||
IPrimitiveType<Date> lowerBound = theLowerBound;
|
||||
|
@ -350,22 +373,20 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
/**
|
||||
* Sets the range from a pair of dates, inclusive on both ends
|
||||
*
|
||||
* @param theLowerBound
|
||||
* A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound
|
||||
* A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theLowerBound A qualified date param representing the lower date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
* @param theUpperBound A qualified date param representing the upper date bound (optionally may include time), e.g.
|
||||
* "2011-02-22" or "2011-02-22T13:12:00Z". Will be treated inclusively. Either theLowerBound or
|
||||
* theUpperBound may both be populated, or one may be null, but it is not valid for both to be null.
|
||||
*/
|
||||
public void setRangeFromDatesInclusive(String theLowerBound, String theUpperBound) {
|
||||
DateParam lowerBound = theLowerBound != null
|
||||
? new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound)
|
||||
: null;
|
||||
? new DateParam(GREATERTHAN_OR_EQUALS, theLowerBound)
|
||||
: null;
|
||||
DateParam upperBound = theUpperBound != null
|
||||
? new DateParam(LESSTHAN_OR_EQUALS, theUpperBound)
|
||||
: null;
|
||||
? new DateParam(LESSTHAN_OR_EQUALS, theUpperBound)
|
||||
: null;
|
||||
if (isNotBlank(theLowerBound) && isNotBlank(theUpperBound) && theLowerBound.equals(theUpperBound)) {
|
||||
lowerBound.setPrefix(EQUAL);
|
||||
upperBound.setPrefix(EQUAL);
|
||||
|
@ -373,14 +394,22 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
validateAndSet(lowerBound, upperBound);
|
||||
}
|
||||
|
||||
public DateRangeParam setUpperBound(DateParam theUpperBound) {
|
||||
validateAndSet(myLowerBound, theUpperBound);
|
||||
/**
|
||||
* Sets the upper bound using a string that is compliant with
|
||||
* FHIR dateTime format (ISO-8601).
|
||||
* <p>
|
||||
* This upper bound is assumed to have a <code>le</code>
|
||||
* (less than or equals) modifier.
|
||||
* </p>
|
||||
*/
|
||||
public DateRangeParam setUpperBound(String theUpperBound) {
|
||||
setUpperBound(new DateParam(LESSTHAN_OR_EQUALS, theUpperBound));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValuesAsQueryTokens(FhirContext theContext, String theParamName, List<QualifiedParamList> theParameters)
|
||||
throws InvalidRequestException {
|
||||
throws InvalidRequestException {
|
||||
|
||||
boolean haveHadUnqualifiedParameter = false;
|
||||
for (QualifiedParamList paramList : theParameters) {
|
||||
|
@ -413,24 +442,6 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof DateRangeParam)) {
|
||||
return false;
|
||||
}
|
||||
DateRangeParam other = (DateRangeParam) obj;
|
||||
return Objects.equals(myLowerBound, other.myLowerBound) &&
|
||||
Objects.equals(myUpperBound, other.myUpperBound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(myLowerBound, myUpperBound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder b = new StringBuilder();
|
||||
|
@ -463,8 +474,8 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
if (hasBound(lowerBound) && hasBound(upperBound)) {
|
||||
if (lowerBound.getValue().getTime() > upperBound.getValue().getTime()) {
|
||||
throw new DataFormatException(format(
|
||||
"Lower bound of %s is after upper bound of %s",
|
||||
lowerBound.getValueAsString(), upperBound.getValueAsString()));
|
||||
"Lower bound of %s is after upper bound of %s",
|
||||
lowerBound.getValueAsString(), upperBound.getValueAsString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -473,13 +484,13 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
lowerBound.setPrefix(GREATERTHAN_OR_EQUALS);
|
||||
}
|
||||
switch (lowerBound.getPrefix()) {
|
||||
case GREATERTHAN:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
default:
|
||||
break;
|
||||
case LESSTHAN:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
throw new DataFormatException("Lower bound comparator must be > or >=, can not be " + lowerBound.getPrefix().getValue());
|
||||
case GREATERTHAN:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
default:
|
||||
break;
|
||||
case LESSTHAN:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
throw new DataFormatException("Lower bound comparator must be > or >=, can not be " + lowerBound.getPrefix().getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -488,13 +499,13 @@ public class DateRangeParam implements IQueryParameterAnd<DateParam> {
|
|||
upperBound.setPrefix(LESSTHAN_OR_EQUALS);
|
||||
}
|
||||
switch (upperBound.getPrefix()) {
|
||||
case LESSTHAN:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
default:
|
||||
break;
|
||||
case GREATERTHAN:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
throw new DataFormatException("Upper bound comparator must be < or <=, can not be " + upperBound.getPrefix().getValue());
|
||||
case LESSTHAN:
|
||||
case LESSTHAN_OR_EQUALS:
|
||||
default:
|
||||
break;
|
||||
case GREATERTHAN:
|
||||
case GREATERTHAN_OR_EQUALS:
|
||||
throw new DataFormatException("Upper bound comparator must be < or <=, can not be " + upperBound.getPrefix().getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ public class ReflectionUtil {
|
|||
public static Class<?> getGenericCollectionTypeOfMethodParameter(Method theMethod, int theParamIndex) {
|
||||
Class<?> type;
|
||||
Type genericParameterType = theMethod.getGenericParameterTypes()[theParamIndex];
|
||||
if (Class.class.equals(genericParameterType)) {
|
||||
if (Class.class.equals(genericParameterType) || Class.class.equals(genericParameterType.getClass())) {
|
||||
return null;
|
||||
}
|
||||
ParameterizedType collectionType = (ParameterizedType) genericParameterType;
|
||||
|
|
|
@ -57,6 +57,7 @@ ca.uhn.fhir.validation.ValidationResult.noIssuesDetected=No issues detected duri
|
|||
|
||||
# JPA Messages
|
||||
|
||||
|
||||
ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.resourceVersionConstraintFailure=The operation has failed with a version constraint failure. This generally means that two clients/threads were trying to update the same resource at the same time, and this request was chosen as the failing request.
|
||||
ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.resourceIndexedCompositeStringUniqueConstraintFailure=The operation has failed with a unique index constraint failure. This probably means that the operation was trying to create/update a resource that would have resulted in a duplicate value for a unique index.
|
||||
|
||||
|
@ -90,9 +91,13 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulUpdate=Successfully update
|
|||
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulDeletes=Successfully deleted {0} resource(s) in {1}ms
|
||||
ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidSearchParameter=Unknown search parameter "{0}". Value search parameters for this search are: {1}
|
||||
|
||||
ca.uhn.fhir.jpa.dao.BaseSearchParamExtractor.failedToExtractPaths=Failed to extract values from resource using FHIRPath "{0}": {1}
|
||||
|
||||
ca.uhn.fhir.jpa.dao.SearchBuilder.invalidQuantityPrefix=Unable to handle quantity prefix "{0}" for value: {1}
|
||||
ca.uhn.fhir.jpa.dao.SearchBuilder.invalidNumberPrefix=Unable to handle number prefix "{0}" for value: {1}
|
||||
|
||||
ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4.invalidSearchParamExpression=The expression "{0}" can not be evaluated and may be invalid: {1}
|
||||
|
||||
ca.uhn.fhir.jpa.provider.BaseJpaProvider.cantCombintAtAndSince=Unable to combine _at and _since parameters for history operation
|
||||
|
||||
ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl.cannotCreateDuplicateConceptMapUrl=Can not create multiple ConceptMap resources with ConceptMap.url "{0}", already have one with resource ID: {1}
|
||||
|
|
|
@ -743,6 +743,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
private Class<? extends IBaseBundle> myReturnType;
|
||||
private IPrimitiveType mySince;
|
||||
private Class<? extends IBaseResource> myType;
|
||||
private DateRangeParam myAt;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
|
@ -752,6 +753,12 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHistoryTyped at(DateRangeParam theDateRangeParam) {
|
||||
myAt = theDateRangeParam;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHistoryTyped count(Integer theCount) {
|
||||
myCount = theCount;
|
||||
|
@ -774,7 +781,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
id = null;
|
||||
}
|
||||
|
||||
HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(myContext, resourceName, id, mySince, myCount);
|
||||
HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(myContext, resourceName, id, mySince, myCount, myAt);
|
||||
|
||||
IClientResponseHandler handler;
|
||||
handler = new ResourceResponseHandler(myReturnType, getPreferResponseTypes(myType));
|
||||
|
|
|
@ -26,6 +26,8 @@ import java.lang.reflect.Method;
|
|||
import java.lang.reflect.Modifier;
|
||||
import java.util.Date;
|
||||
|
||||
import ca.uhn.fhir.rest.param.DateParam;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
|
@ -96,7 +98,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
|
||||
String historyId = id != null ? id.getIdPart() : null;
|
||||
HttpGetClientInvocation retVal = createHistoryInvocation(getContext(), resourceName, historyId, null, null);
|
||||
HttpGetClientInvocation retVal = createHistoryInvocation(getContext(), resourceName, historyId, null, null, null);
|
||||
|
||||
if (theArgs != null) {
|
||||
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||
|
@ -108,7 +110,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
public static HttpGetClientInvocation createHistoryInvocation(FhirContext theContext, String theResourceName, String theId, IPrimitiveType<Date> theSince, Integer theLimit) {
|
||||
public static HttpGetClientInvocation createHistoryInvocation(FhirContext theContext, String theResourceName, String theId, IPrimitiveType<Date> theSince, Integer theLimit, DateRangeParam theAt) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
if (theResourceName != null) {
|
||||
b.append(theResourceName);
|
||||
|
@ -129,8 +131,18 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
if (theLimit != null) {
|
||||
b.append(haveParam ? '&' : '?');
|
||||
haveParam = true;
|
||||
b.append(Constants.PARAM_COUNT).append('=').append(theLimit);
|
||||
}
|
||||
if (theAt != null) {
|
||||
for (DateParam next : theAt.getValuesAsQueryTokens()) {
|
||||
b.append(haveParam ? '&' : '?');
|
||||
haveParam = true;
|
||||
b.append(Constants.PARAM_AT);
|
||||
b.append("=");
|
||||
b.append(next.getValueAsQueryToken(theContext));
|
||||
}
|
||||
}
|
||||
|
||||
HttpGetClientInvocation retVal = new HttpGetClientInvocation(theContext, b.toString());
|
||||
return retVal;
|
||||
|
|
|
@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.dao;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
@ -47,6 +48,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
private DaoConfig myDaoConfig;
|
||||
@Autowired
|
||||
private ISearchParamRegistry mySearchParamRegistry;
|
||||
|
||||
public BaseSearchParamExtractor() {
|
||||
super();
|
||||
}
|
||||
|
@ -73,31 +75,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
return refs;
|
||||
}
|
||||
|
||||
protected List<Object> extractValues(String thePaths, IBaseResource theResource) {
|
||||
List<Object> values = new ArrayList<Object>();
|
||||
String[] nextPathsSplit = SPLIT.split(thePaths);
|
||||
FhirTerser t = myContext.newTerser();
|
||||
for (String nextPath : nextPathsSplit) {
|
||||
String nextPathTrimmed = nextPath.trim();
|
||||
try {
|
||||
List<Object> allValues = t.getValues(theResource, nextPathTrimmed);
|
||||
for (Object next : allValues) {
|
||||
if (next instanceof IBaseExtension) {
|
||||
IBaseDatatype value = ((IBaseExtension) next).getValue();
|
||||
if (value != null) {
|
||||
values.add(value);
|
||||
}
|
||||
} else {
|
||||
values.add(next);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource);
|
||||
ourLog.warn("Failed to index values from path[{}] in resource type[{}]: {}", new Object[] {nextPathTrimmed, def.getName(), e.toString(), e});
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
protected abstract List<Object> extractValues(String thePaths, IBaseResource theResource);
|
||||
|
||||
protected FhirContext getContext() {
|
||||
return myContext;
|
||||
|
|
|
@ -144,6 +144,7 @@ public class DaoConfig {
|
|||
private boolean myExpungeEnabled;
|
||||
private int myReindexThreadCount;
|
||||
private Set<String> myBundleTypesAllowedForStorage;
|
||||
private boolean myValidateSearchParameterExpressionsOnSave = true;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -786,7 +787,6 @@ public class DaoConfig {
|
|||
this.myAllowContainsSearches = theAllowContainsSearches;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If set to <code>true</code> (default is <code>false</code>) the server will allow
|
||||
* resources to have references to external servers. For example if this server is
|
||||
|
@ -1188,6 +1188,34 @@ public class DaoConfig {
|
|||
myUniqueIndexesEnabled = theUniqueIndexesEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* If <code>true</code> (default is <code>true</code>), before allowing a
|
||||
* SearchParameter resource to be stored (create, update, etc.) the
|
||||
* expression will be performed against an empty resource to ensure that
|
||||
* the FHIRPath executor is able to process it.
|
||||
* <p>
|
||||
* This should proabably always be set to true, but is configurable
|
||||
* in order to support some unit tests.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isValidateSearchParameterExpressionsOnSave() {
|
||||
return myValidateSearchParameterExpressionsOnSave;
|
||||
}
|
||||
|
||||
/**
|
||||
* If <code>true</code> (default is <code>true</code>), before allowing a
|
||||
* SearchParameter resource to be stored (create, update, etc.) the
|
||||
* expression will be performed against an empty resource to ensure that
|
||||
* the FHIRPath executor is able to process it.
|
||||
* <p>
|
||||
* This should proabably always be set to true, but is configurable
|
||||
* in order to support some unit tests.
|
||||
* </p>
|
||||
*/
|
||||
public void setValidateSearchParameterExpressionsOnSave(boolean theValidateSearchParameterExpressionsOnSave) {
|
||||
myValidateSearchParameterExpressionsOnSave = theValidateSearchParameterExpressionsOnSave;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not call this method, it exists only for legacy reasons. It
|
||||
* will be removed in a future version. Configure the page size on your
|
||||
|
|
|
@ -29,14 +29,8 @@ import ca.uhn.fhir.model.dstu2.resource.SearchParameter;
|
|||
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SearchParamTypeEnum;
|
||||
import ca.uhn.fhir.model.primitive.BoundCodeDt;
|
||||
import ca.uhn.fhir.model.primitive.CodeDt;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -83,7 +77,7 @@ public class FhirResourceDaoSearchParameterDstu2 extends FhirResourceDaoDstu2<Se
|
|||
FhirContext context = getContext();
|
||||
SearchParamTypeEnum type = theResource.getTypeElement().getValueAsEnum();
|
||||
|
||||
FhirResourceDaoSearchParameterR4.validateSearchParam(type, status, base, expression, context);
|
||||
FhirResourceDaoSearchParameterR4.validateSearchParam(type, status, base, expression, context, getConfig());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -19,33 +19,40 @@ package ca.uhn.fhir.jpa.dao;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import javax.measure.quantity.Quantity;
|
||||
import javax.measure.unit.NonSI;
|
||||
import javax.measure.unit.Unit;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.entity.*;
|
||||
import ca.uhn.fhir.model.api.*;
|
||||
import ca.uhn.fhir.model.api.IDatatype;
|
||||
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
|
||||
import ca.uhn.fhir.model.api.IValueSetEnumBinder;
|
||||
import ca.uhn.fhir.model.base.composite.BaseHumanNameDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.*;
|
||||
import ca.uhn.fhir.model.dstu2.composite.BoundCodeableConceptDt;
|
||||
import ca.uhn.fhir.model.dstu2.resource.*;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Conformance.RestSecurity;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Location;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Patient.Communication;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Questionnaire;
|
||||
import ca.uhn.fhir.model.dstu2.resource.ValueSet;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.RestfulSecurityServiceEnum;
|
||||
import ca.uhn.fhir.model.primitive.*;
|
||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.hl7.fhir.instance.model.api.IBaseDatatype;
|
||||
import org.hl7.fhir.instance.model.api.IBaseExtension;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import javax.measure.quantity.Quantity;
|
||||
import javax.measure.unit.NonSI;
|
||||
import javax.measure.unit.Unit;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implements ISearchParamExtractor {
|
||||
|
||||
|
@ -87,7 +94,7 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
|
|||
*/
|
||||
@Override
|
||||
public Set<ResourceIndexedSearchParamDate> extractSearchParamDates(ResourceTable theEntity, IBaseResource theResource) {
|
||||
HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<ResourceIndexedSearchParamDate>();
|
||||
HashSet<ResourceIndexedSearchParamDate> retVal = new HashSet<>();
|
||||
|
||||
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
|
||||
for (RuntimeSearchParam nextSpDef : searchParams) {
|
||||
|
@ -626,6 +633,35 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Object> extractValues(String thePaths, IBaseResource theResource) {
|
||||
List<Object> values = new ArrayList<>();
|
||||
String[] nextPathsSplit = SPLIT.split(thePaths);
|
||||
FhirTerser t = getContext().newTerser();
|
||||
for (String nextPath : nextPathsSplit) {
|
||||
String nextPathTrimmed = nextPath.trim();
|
||||
List<Object> allValues;
|
||||
try {
|
||||
allValues = t.getValues(theResource, nextPathTrimmed);
|
||||
} catch (Exception e) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseSearchParamExtractor.class, "failedToExtractPaths", nextPath, e.toString());
|
||||
throw new InternalErrorException(msg, e);
|
||||
}
|
||||
for (Object next : allValues) {
|
||||
if (next instanceof IBaseExtension) {
|
||||
IBaseDatatype value = ((IBaseExtension) next).getValue();
|
||||
if (value != null) {
|
||||
values.add(value);
|
||||
}
|
||||
} else {
|
||||
values.add(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
private static <T extends Enum<?>> String extractSystem(BoundCodeDt<T> theBoundCode) {
|
||||
if (theBoundCode.getValueAsEnum() != null) {
|
||||
IValueSetEnumBinder<T> binder = theBoundCode.getBinder();
|
||||
|
|
|
@ -74,7 +74,7 @@ public class FhirResourceDaoSearchParameterDstu3 extends FhirResourceDaoDstu3<Se
|
|||
FhirContext context = getContext();
|
||||
Enumerations.SearchParamType type = theResource.getType();
|
||||
|
||||
FhirResourceDaoSearchParameterR4.validateSearchParam(type, status, base, expression, context);
|
||||
FhirResourceDaoSearchParameterR4.validateSearchParam(type, status, base, expression, context, getConfig());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,17 +19,15 @@ package ca.uhn.fhir.jpa.dao.dstu3;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.trim;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.measure.unit.NonSI;
|
||||
import javax.measure.unit.Unit;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.dao.*;
|
||||
import ca.uhn.fhir.jpa.entity.*;
|
||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext;
|
||||
|
@ -41,16 +39,18 @@ import org.hl7.fhir.dstu3.model.Location.LocationPositionComponent;
|
|||
import org.hl7.fhir.dstu3.model.Patient.PatientCommunicationComponent;
|
||||
import org.hl7.fhir.dstu3.utils.FHIRPathEngine;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.measure.unit.NonSI;
|
||||
import javax.measure.unit.Unit;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import ca.uhn.fhir.context.*;
|
||||
import ca.uhn.fhir.jpa.dao.*;
|
||||
import ca.uhn.fhir.jpa.entity.*;
|
||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import static org.apache.commons.lang3.StringUtils.*;
|
||||
|
||||
public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implements ISearchParamExtractor {
|
||||
|
||||
|
@ -61,11 +61,6 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
|
|||
|
||||
private HapiWorkerContext myWorkerContext;
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
myWorkerContext = new HapiWorkerContext(getContext(), myValidationSupport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
|
@ -78,6 +73,17 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
|
|||
myValidationSupport = theValidationSupport;
|
||||
}
|
||||
|
||||
private void addQuantity(ResourceTable theEntity, HashSet<ResourceIndexedSearchParamQuantity> retVal, String resourceName, Quantity nextValue) {
|
||||
if (!nextValue.getValueElement().isEmpty()) {
|
||||
BigDecimal nextValueValue = nextValue.getValueElement().getValue();
|
||||
String nextValueString = nextValue.getSystemElement().getValueAsString();
|
||||
String nextValueCode = nextValue.getCode();
|
||||
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(resourceName, nextValueValue, nextValueString, nextValueCode);
|
||||
nextEntity.setResource(theEntity);
|
||||
retVal.add(nextEntity);
|
||||
}
|
||||
}
|
||||
|
||||
private void addSearchTerm(ResourceTable theEntity, Set<ResourceIndexedSearchParamString> retVal, String resourceName, String searchTerm) {
|
||||
if (isBlank(searchTerm)) {
|
||||
return;
|
||||
|
@ -100,6 +106,23 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
|
|||
retVal.add(nextEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PathAndRef> extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) {
|
||||
ArrayList<PathAndRef> retVal = new ArrayList<PathAndRef>();
|
||||
|
||||
String[] nextPathsSplit = SPLIT.split(theNextSpDef.getPath());
|
||||
for (String path : nextPathsSplit) {
|
||||
path = path.trim();
|
||||
if (isNotBlank(path)) {
|
||||
for (Object next : extractValues(path, theResource)) {
|
||||
retVal.add(new PathAndRef(path, next));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
|
||||
// TODO: implement
|
||||
|
@ -200,7 +223,7 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
|
|||
*/
|
||||
@Override
|
||||
public HashSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(ResourceTable theEntity, IBaseResource theResource) {
|
||||
HashSet<ResourceIndexedSearchParamNumber> retVal = new HashSet<ResourceIndexedSearchParamNumber>();
|
||||
HashSet<ResourceIndexedSearchParamNumber> retVal = new HashSet<>();
|
||||
|
||||
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
|
||||
for (RuntimeSearchParam nextSpDef : searchParams) {
|
||||
|
@ -349,17 +372,6 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private void addQuantity(ResourceTable theEntity, HashSet<ResourceIndexedSearchParamQuantity> retVal, String resourceName, Quantity nextValue) {
|
||||
if (!nextValue.getValueElement().isEmpty()) {
|
||||
BigDecimal nextValueValue = nextValue.getValueElement().getValue();
|
||||
String nextValueString = nextValue.getSystemElement().getValueAsString();
|
||||
String nextValueCode = nextValue.getCode();
|
||||
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(resourceName, nextValueValue, nextValueString, nextValueCode);
|
||||
nextEntity.setResource(theEntity);
|
||||
retVal.add(nextEntity);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
|
@ -674,14 +686,14 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
|
|||
}
|
||||
|
||||
private void extractTokensFromCodeableConcept(List<String> theSystems, List<String> theCodes, CodeableConcept theCodeableConcept, ResourceTable theEntity,
|
||||
Set<BaseResourceIndexedSearchParam> theListToPopulate, RuntimeSearchParam theParameterDef) {
|
||||
Set<BaseResourceIndexedSearchParam> theListToPopulate, RuntimeSearchParam theParameterDef) {
|
||||
for (Coding nextCoding : theCodeableConcept.getCoding()) {
|
||||
extractTokensFromCoding(theSystems, theCodes, theEntity, theListToPopulate, theParameterDef, nextCoding);
|
||||
}
|
||||
}
|
||||
|
||||
private void extractTokensFromCoding(List<String> theSystems, List<String> theCodes, ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> theListToPopulate,
|
||||
RuntimeSearchParam theParameterDef, Coding nextCoding) {
|
||||
RuntimeSearchParam theParameterDef, Coding nextCoding) {
|
||||
if (nextCoding != null && !nextCoding.isEmpty()) {
|
||||
|
||||
String nextSystem = nextCoding.getSystemElement().getValueAsString();
|
||||
|
@ -706,16 +718,18 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
|
|||
FHIRPathEngine fp = new FHIRPathEngine(myWorkerContext);
|
||||
|
||||
List<Object> values = new ArrayList<>();
|
||||
try {
|
||||
String[] nextPathsSplit = SPLIT.split(thePaths);
|
||||
for (String nextPath : nextPathsSplit) {
|
||||
List<Base> allValues = fp.evaluate((Base) theResource, trim(nextPath));
|
||||
if (allValues.isEmpty() == false) {
|
||||
values.addAll(allValues);
|
||||
}
|
||||
String[] nextPathsSplit = SPLIT.split(thePaths);
|
||||
for (String nextPath : nextPathsSplit) {
|
||||
List<Base> allValues;
|
||||
try {
|
||||
allValues = fp.evaluate((Base) theResource, trim(nextPath));
|
||||
} catch (FHIRException e) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseSearchParamExtractor.class, "failedToExtractPaths", nextPath, e.toString());
|
||||
throw new InternalErrorException(msg, e);
|
||||
}
|
||||
if (allValues.isEmpty() == false) {
|
||||
values.addAll(allValues);
|
||||
}
|
||||
} catch (FHIRException e) {
|
||||
throw new InternalErrorException(e);
|
||||
}
|
||||
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
|
@ -730,28 +744,16 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
|
|||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PathAndRef> extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) {
|
||||
ArrayList<PathAndRef> retVal = new ArrayList<PathAndRef>();
|
||||
|
||||
String[] nextPathsSplit = SPLIT.split(theNextSpDef.getPath());
|
||||
for (String path : nextPathsSplit) {
|
||||
path = path.trim();
|
||||
if (isNotBlank(path)) {
|
||||
for (Object next : extractValues(path, theResource)) {
|
||||
retVal.add(new PathAndRef(path, next));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setValidationSupportForTesting(org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport theValidationSupport) {
|
||||
myValidationSupport = theValidationSupport;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
myWorkerContext = new HapiWorkerContext(getContext(), myValidationSupport);
|
||||
}
|
||||
|
||||
private static <T extends Enum<?>> String extractSystem(Enumeration<T> theBoundCode) {
|
||||
if (theBoundCode.getValue() != null) {
|
||||
return theBoundCode.getEnumFactory().toSystem(theBoundCode.getValue());
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
package ca.uhn.fhir.jpa.dao.r4;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.jpa.dao.BaseSearchParamExtractor;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSearchParameter;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.ElementUtil;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -76,10 +80,10 @@ public class FhirResourceDaoSearchParameterR4 extends FhirResourceDaoR4<SearchPa
|
|||
FhirContext context = getContext();
|
||||
Enum<?> type = theResource.getType();
|
||||
|
||||
FhirResourceDaoSearchParameterR4.validateSearchParam(type, status, base, expression, context);
|
||||
FhirResourceDaoSearchParameterR4.validateSearchParam(type, status, base, expression, context, getConfig());
|
||||
}
|
||||
|
||||
public static void validateSearchParam(Enum<?> theType, Enum<?> theStatus, List<? extends IPrimitiveType> theBase, String theExpression, FhirContext theContext) {
|
||||
public static void validateSearchParam(Enum<?> theType, Enum<?> theStatus, List<? extends IPrimitiveType> theBase, String theExpression, FhirContext theContext, DaoConfig theDaoConfig) {
|
||||
if (theStatus == null) {
|
||||
throw new UnprocessableEntityException("SearchParameter.status is missing or invalid");
|
||||
}
|
||||
|
@ -116,6 +120,17 @@ public class FhirResourceDaoSearchParameterR4 extends FhirResourceDaoR4<SearchPa
|
|||
throw new UnprocessableEntityException("Invalid SearchParameter.expression value \"" + nextPath + "\": " + e.getMessage());
|
||||
}
|
||||
|
||||
if (theContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
|
||||
if (theDaoConfig.isValidateSearchParameterExpressionsOnSave()) {
|
||||
IBaseResource temporaryInstance = theContext.getResourceDefinition(resourceName).newInstance();
|
||||
try {
|
||||
theContext.newFluentPath().evaluate(temporaryInstance, nextPath, IBase.class);
|
||||
} catch (Exception e) {
|
||||
String msg = theContext.getLocalizer().getMessage(FhirResourceDaoSearchParameterR4.class, "invalidSearchParamExpression", nextPath, e.getMessage());
|
||||
throw new UnprocessableEntityException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // if have expression
|
||||
|
|
|
@ -19,17 +19,21 @@ package ca.uhn.fhir.jpa.dao.r4;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import javax.measure.unit.NonSI;
|
||||
import javax.measure.unit.Unit;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.dao.*;
|
||||
import ca.uhn.fhir.jpa.entity.*;
|
||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.context.IWorkerContext;
|
||||
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
|
@ -38,17 +42,15 @@ import org.hl7.fhir.r4.model.Enumeration;
|
|||
import org.hl7.fhir.r4.model.Location.LocationPositionComponent;
|
||||
import org.hl7.fhir.r4.model.Patient.PatientCommunicationComponent;
|
||||
import org.hl7.fhir.r4.utils.FHIRPathEngine;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import javax.measure.unit.NonSI;
|
||||
import javax.measure.unit.Unit;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import ca.uhn.fhir.context.*;
|
||||
import ca.uhn.fhir.jpa.dao.*;
|
||||
import ca.uhn.fhir.jpa.entity.*;
|
||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements ISearchParamExtractor {
|
||||
|
||||
|
@ -69,6 +71,17 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
|
|||
myValidationSupport = theValidationSupport;
|
||||
}
|
||||
|
||||
private void addQuantity(ResourceTable theEntity, HashSet<ResourceIndexedSearchParamQuantity> retVal, String resourceName, Quantity nextValue) {
|
||||
if (!nextValue.getValueElement().isEmpty()) {
|
||||
BigDecimal nextValueValue = nextValue.getValueElement().getValue();
|
||||
String nextValueString = nextValue.getSystemElement().getValueAsString();
|
||||
String nextValueCode = nextValue.getCode();
|
||||
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(resourceName, nextValueValue, nextValueString, nextValueCode);
|
||||
nextEntity.setResource(theEntity);
|
||||
retVal.add(nextEntity);
|
||||
}
|
||||
}
|
||||
|
||||
private void addSearchTerm(ResourceTable theEntity, Set<ResourceIndexedSearchParamString> retVal, String resourceName, String searchTerm) {
|
||||
if (isBlank(searchTerm)) {
|
||||
return;
|
||||
|
@ -91,6 +104,23 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
|
|||
retVal.add(nextEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PathAndRef> extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) {
|
||||
ArrayList<PathAndRef> retVal = new ArrayList<>();
|
||||
|
||||
String[] nextPathsSplit = SPLIT.split(theNextSpDef.getPath());
|
||||
for (String path : nextPathsSplit) {
|
||||
path = path.trim();
|
||||
if (isNotBlank(path)) {
|
||||
for (Object next : extractValues(path, theResource)) {
|
||||
retVal.add(new PathAndRef(path, next));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
|
||||
// TODO: implement
|
||||
|
@ -336,17 +366,6 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private void addQuantity(ResourceTable theEntity, HashSet<ResourceIndexedSearchParamQuantity> retVal, String resourceName, Quantity nextValue) {
|
||||
if (!nextValue.getValueElement().isEmpty()) {
|
||||
BigDecimal nextValueValue = nextValue.getValueElement().getValue();
|
||||
String nextValueString = nextValue.getSystemElement().getValueAsString();
|
||||
String nextValueCode = nextValue.getCode();
|
||||
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(resourceName, nextValueValue, nextValueString, nextValueCode);
|
||||
nextEntity.setResource(theEntity);
|
||||
retVal.add(nextEntity);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
|
@ -658,14 +677,14 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
|
|||
}
|
||||
|
||||
private void extractTokensFromCodeableConcept(List<String> theSystems, List<String> theCodes, CodeableConcept theCodeableConcept, ResourceTable theEntity,
|
||||
Set<BaseResourceIndexedSearchParam> theListToPopulate, RuntimeSearchParam theParameterDef) {
|
||||
Set<BaseResourceIndexedSearchParam> theListToPopulate, RuntimeSearchParam theParameterDef) {
|
||||
for (Coding nextCoding : theCodeableConcept.getCoding()) {
|
||||
extractTokensFromCoding(theSystems, theCodes, theEntity, theListToPopulate, theParameterDef, nextCoding);
|
||||
}
|
||||
}
|
||||
|
||||
private void extractTokensFromCoding(List<String> theSystems, List<String> theCodes, ResourceTable theEntity, Set<BaseResourceIndexedSearchParam> theListToPopulate,
|
||||
RuntimeSearchParam theParameterDef, Coding nextCoding) {
|
||||
RuntimeSearchParam theParameterDef, Coding nextCoding) {
|
||||
if (nextCoding != null && !nextCoding.isEmpty()) {
|
||||
|
||||
String nextSystem = nextCoding.getSystemElement().getValueAsString();
|
||||
|
@ -691,16 +710,18 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
|
|||
FHIRPathEngine fp = new FHIRPathEngine(worker);
|
||||
|
||||
List<Object> values = new ArrayList<>();
|
||||
try {
|
||||
String[] nextPathsSplit = SPLIT.split(thePaths);
|
||||
for (String nextPath : nextPathsSplit) {
|
||||
List<Base> allValues = fp.evaluate((Base) theResource, nextPath);
|
||||
if (allValues.isEmpty() == false) {
|
||||
values.addAll(allValues);
|
||||
}
|
||||
String[] nextPathsSplit = SPLIT.split(thePaths);
|
||||
for (String nextPath : nextPathsSplit) {
|
||||
List<Base> allValues;
|
||||
try {
|
||||
allValues = fp.evaluate((Base) theResource, nextPath);
|
||||
} catch (FHIRException e) {
|
||||
String msg = getContext().getLocalizer().getMessage(BaseSearchParamExtractor.class, "failedToExtractPaths", nextPath, e.toString());
|
||||
throw new InternalErrorException(msg, e);
|
||||
}
|
||||
if (allValues.isEmpty() == false) {
|
||||
values.addAll(allValues);
|
||||
}
|
||||
} catch (FHIRException e) {
|
||||
throw new InternalErrorException(e);
|
||||
}
|
||||
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
|
@ -715,23 +736,6 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
|
|||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PathAndRef> extractResourceLinks(IBaseResource theResource, RuntimeSearchParam theNextSpDef) {
|
||||
ArrayList<PathAndRef> retVal = new ArrayList<>();
|
||||
|
||||
String[] nextPathsSplit = SPLIT.split(theNextSpDef.getPath());
|
||||
for (String path : nextPathsSplit) {
|
||||
path = path.trim();
|
||||
if (isNotBlank(path)) {
|
||||
for (Object next : extractValues(path, theResource)) {
|
||||
retVal.add(new PathAndRef(path, next));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setValidationSupportForTesting(org.hl7.fhir.r4.hapi.ctx.IValidationSupport theValidationSupport) {
|
||||
myValidationSupport = theValidationSupport;
|
||||
|
|
|
@ -62,6 +62,9 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
|||
@Qualifier("mySearchParameterDaoDstu2")
|
||||
protected IFhirResourceDao<SearchParameter> mySearchParameterDao;
|
||||
@Autowired
|
||||
@Qualifier("myCommunicationDaoDstu2")
|
||||
protected IFhirResourceDao<Communication> myCommunicationDao;
|
||||
@Autowired
|
||||
@Qualifier("myBundleDaoDstu2")
|
||||
protected IFhirResourceDao<Bundle> myBundleDao;
|
||||
@Autowired
|
||||
|
|
|
@ -13,14 +13,12 @@ import ca.uhn.fhir.model.dstu2.valueset.*;
|
|||
import ca.uhn.fhir.model.primitive.*;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.*;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.*;
|
||||
import org.mockito.internal.util.collections.ListUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -37,6 +35,12 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
|
|||
myDaoConfig.setDefaultSearchParamsCanBeOverridden(new DaoConfig().isDefaultSearchParamsCanBeOverridden());
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
myDaoConfig.setValidateSearchParameterExpressionsOnSave(new DaoConfig().isValidateSearchParameterExpressionsOnSave());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateInvalidNoBase() {
|
||||
SearchParameter fooSp = new SearchParameter();
|
||||
|
@ -53,6 +57,31 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexFailsIfInvalidSearchParameterExists() {
|
||||
myDaoConfig.setValidateSearchParameterExpressionsOnSave(false);
|
||||
|
||||
SearchParameter threadIdSp = new SearchParameter();
|
||||
threadIdSp.setBase(ResourceTypeEnum.COMMUNICATION);
|
||||
threadIdSp.setCode("has-attachments");
|
||||
threadIdSp.setType(SearchParamTypeEnum.REFERENCE);
|
||||
threadIdSp.setXpath("Communication.payload[1].contentAttachment is not null");
|
||||
threadIdSp.setXpathUsage(XPathUsageTypeEnum.NORMAL);
|
||||
threadIdSp.setStatus(ConformanceResourceStatusEnum.ACTIVE);
|
||||
mySearchParameterDao.create(threadIdSp, mySrd);
|
||||
mySearchParamRegsitry.forceRefresh();
|
||||
|
||||
Communication com = new Communication();
|
||||
com.setStatus(CommunicationStatusEnum.IN_PROGRESS);
|
||||
try {
|
||||
myCommunicationDao.create(com, mySrd);
|
||||
fail();
|
||||
} catch (InternalErrorException e) {
|
||||
assertThat(e.getMessage(), startsWith("Failed to extract values from resource using FHIRPath \"Communication.payload[1].contentAttachment is not null\": ca.uhn"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateInvalidParamInvalidResourceName() {
|
||||
SearchParameter fooSp = new SearchParameter();
|
||||
|
|
|
@ -168,6 +168,9 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
|
|||
@Qualifier("myPatientDaoDstu3")
|
||||
protected IFhirResourceDaoPatient<Patient> myPatientDao;
|
||||
@Autowired
|
||||
@Qualifier("myCommunicationDaoDstu3")
|
||||
protected IFhirResourceDao<Communication> myCommunicationDao;
|
||||
@Autowired
|
||||
@Qualifier("myPractitionerDaoDstu3")
|
||||
protected IFhirResourceDao<Practitioner> myPractitionerDao;
|
||||
@Autowired
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package ca.uhn.fhir.jpa.dao.dstu3;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.*;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
|
@ -12,6 +14,7 @@ import org.hl7.fhir.dstu3.model.Appointment.AppointmentStatus;
|
|||
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -29,6 +32,11 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
|
|||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
myDaoConfig.setValidateSearchParameterExpressionsOnSave(new DaoConfig().isValidateSearchParameterExpressionsOnSave());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateInvalidNoBase() {
|
||||
SearchParameter fooSp = new SearchParameter();
|
||||
|
@ -211,6 +219,47 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexFailsIfInvalidSearchParameterExists() {
|
||||
myDaoConfig.setValidateSearchParameterExpressionsOnSave(false);
|
||||
|
||||
SearchParameter threadIdSp = new SearchParameter();
|
||||
threadIdSp.addBase("Communication");
|
||||
threadIdSp.setCode("has-attachments");
|
||||
threadIdSp.setType(Enumerations.SearchParamType.REFERENCE);
|
||||
threadIdSp.setExpression("Communication.payload[1].contentAttachment is not null");
|
||||
threadIdSp.setXpathUsage(SearchParameter.XPathUsageType.NORMAL);
|
||||
threadIdSp.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
||||
mySearchParameterDao.create(threadIdSp, mySrd);
|
||||
mySearchParamRegsitry.forceRefresh();
|
||||
|
||||
Communication com = new Communication();
|
||||
com.setStatus(Communication.CommunicationStatus.INPROGRESS);
|
||||
try {
|
||||
myCommunicationDao.create(com, mySrd);
|
||||
fail();
|
||||
} catch (InternalErrorException e) {
|
||||
assertThat(e.getMessage(), startsWith("Failed to extract values from resource using FHIRPath \"Communication.payload[1].contentAttachment is not null\": org.hl7.fhir"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRejectSearchParamWithInvalidExpression() {
|
||||
SearchParameter threadIdSp = new SearchParameter();
|
||||
threadIdSp.addBase("Communication");
|
||||
threadIdSp.setCode("has-attachments");
|
||||
threadIdSp.setType(Enumerations.SearchParamType.REFERENCE);
|
||||
threadIdSp.setExpression("Communication.payload[1].contentAttachment is not null");
|
||||
threadIdSp.setXpathUsage(SearchParameter.XPathUsageType.NORMAL);
|
||||
threadIdSp.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
||||
try {
|
||||
mySearchParameterDao.create(threadIdSp, mySrd);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), startsWith("The expression \"Communication.payload[1].contentAttachment is not null\" can not be evaluated and may be invalid: "));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See #863
|
||||
*/
|
||||
|
|
|
@ -92,6 +92,9 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
|
|||
@Qualifier("myBundleDaoR4")
|
||||
protected IFhirResourceDao<Bundle> myBundleDao;
|
||||
@Autowired
|
||||
@Qualifier("myCommunicationDaoR4")
|
||||
protected IFhirResourceDao<Communication> myCommunicationDao;
|
||||
@Autowired
|
||||
@Qualifier("myCarePlanDaoR4")
|
||||
protected IFhirResourceDao<CarePlan> myCarePlanDao;
|
||||
@Autowired
|
||||
|
|
|
@ -6,6 +6,7 @@ import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken;
|
|||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.*;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
|
@ -13,6 +14,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
|
|||
import org.hl7.fhir.r4.model.*;
|
||||
import org.hl7.fhir.r4.model.Appointment.AppointmentStatus;
|
||||
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -30,6 +32,11 @@ import static org.junit.Assert.*;
|
|||
public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4SearchCustomSearchParamTest.class);
|
||||
|
||||
@After
|
||||
public void after() {
|
||||
myDaoConfig.setValidateSearchParameterExpressionsOnSave(new DaoConfig().isValidateSearchParameterExpressionsOnSave());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeDisableResultReuse() {
|
||||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||
|
@ -71,7 +78,6 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreateInvalidParamNoPath() {
|
||||
SearchParameter fooSp = new SearchParameter();
|
||||
|
@ -237,6 +243,30 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexFailsIfInvalidSearchParameterExists() {
|
||||
myDaoConfig.setValidateSearchParameterExpressionsOnSave(false);
|
||||
|
||||
SearchParameter threadIdSp = new SearchParameter();
|
||||
threadIdSp.addBase("Communication");
|
||||
threadIdSp.setCode("has-attachments");
|
||||
threadIdSp.setType(Enumerations.SearchParamType.REFERENCE);
|
||||
threadIdSp.setExpression("Communication.payload[1].contentAttachment is not null");
|
||||
threadIdSp.setXpathUsage(SearchParameter.XPathUsageType.NORMAL);
|
||||
threadIdSp.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
||||
mySearchParameterDao.create(threadIdSp, mySrd);
|
||||
mySearchParamRegsitry.forceRefresh();
|
||||
|
||||
Communication com = new Communication();
|
||||
com.setStatus(Communication.CommunicationStatus.INPROGRESS);
|
||||
try {
|
||||
myCommunicationDao.create(com, mySrd);
|
||||
fail();
|
||||
} catch (InternalErrorException e) {
|
||||
assertThat(e.getMessage(), startsWith("Failed to extract values from resource using FHIRPath \"Communication.payload[1].contentAttachment is not null\": org.hl7.fhir"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverrideAndDisableBuiltInSearchParametersWithOverridingDisabled() {
|
||||
myDaoConfig.setDefaultSearchParamsCanBeOverridden(false);
|
||||
|
@ -387,6 +417,23 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
|
|||
assertThat(results, contains(mrId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRejectSearchParamWithInvalidExpression() {
|
||||
SearchParameter threadIdSp = new SearchParameter();
|
||||
threadIdSp.addBase("Communication");
|
||||
threadIdSp.setCode("has-attachments");
|
||||
threadIdSp.setType(Enumerations.SearchParamType.REFERENCE);
|
||||
threadIdSp.setExpression("Communication.payload[1].contentAttachment is not null");
|
||||
threadIdSp.setXpathUsage(SearchParameter.XPathUsageType.NORMAL);
|
||||
threadIdSp.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
||||
try {
|
||||
mySearchParameterDao.create(threadIdSp, mySrd);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), startsWith("The expression \"Communication.payload[1].contentAttachment is not null\" can not be evaluated and may be invalid: "));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchForExtensionReferenceWithNonMatchingTarget() {
|
||||
SearchParameter siblingSp = new SearchParameter();
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"resourceType": "Communication",
|
||||
"meta": {
|
||||
"lastUpdated": "2018-07-20T19:34:56.236+05:30",
|
||||
"tag": [
|
||||
{
|
||||
"system": "systemDefined",
|
||||
"code": "read"
|
||||
}
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"status": "generated"
|
||||
},
|
||||
"extension": [
|
||||
{
|
||||
"url": "http://telus.com/fhir/StructureDefinition/ext-communication-msgOwner",
|
||||
"valueString": "17427"
|
||||
},
|
||||
{
|
||||
"url": "http://telus.com/fhir/StructureDefinition/ext-communication-priority",
|
||||
"valueCode": "normal"
|
||||
},
|
||||
{
|
||||
"url": "http://telus.com/fhir/StructureDefinition/ext-communication-topic",
|
||||
"valueString": "dsads"
|
||||
},
|
||||
{
|
||||
"url": "http://telus.com/fhir/StructureDefinition/ext-communication-state",
|
||||
"valueCode": "draft"
|
||||
},
|
||||
{
|
||||
"url": "http://telus.com/fhir/StructureDefinition/ext-communication-thread-id",
|
||||
"valueString": "bd7bc833-953b-4379-b0b0-be3d898dee40"
|
||||
}
|
||||
],
|
||||
"status": "in-progress",
|
||||
"recipient": [
|
||||
{
|
||||
"reference": "RelatedPerson/17852"
|
||||
}
|
||||
],
|
||||
"sender": {
|
||||
"reference": "RelatedPerson/17427"
|
||||
}
|
||||
}
|
|
@ -122,7 +122,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
|||
*/
|
||||
private String myServerVersion = createPoweredByHeaderProductVersion();
|
||||
private boolean myStarted;
|
||||
private Map<String, IResourceProvider> myTypeToProvider = new HashMap<>();
|
||||
private boolean myUncompressIncomingContents = true;
|
||||
private boolean myUseBrowserFriendlyContentTypes;
|
||||
private ITenantIdentificationStrategy myTenantIdentificationStrategy;
|
||||
|
@ -376,7 +375,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
|||
try {
|
||||
count += findResourceMethods(theProvider, clazz);
|
||||
} catch (ConfigurationException e) {
|
||||
throw new ConfigurationException("Failure scanning class " + clazz.getSimpleName() + ": " + e.getMessage());
|
||||
throw new ConfigurationException("Failure scanning class " + clazz.getSimpleName() + ": " + e.getMessage(), e);
|
||||
}
|
||||
if (count == 0) {
|
||||
throw new ConfigurationException("Did not find any annotated RESTful methods on provider class " + theProvider.getClass().getCanonicalName());
|
||||
|
@ -1365,14 +1364,9 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
|||
throw new NullPointerException("getResourceType() on class '" + rsrcProvider.getClass().getCanonicalName() + "' returned null");
|
||||
}
|
||||
String resourceName = getFhirContext().getResourceDefinition(resourceType).getName();
|
||||
if (myTypeToProvider.containsKey(resourceName)) {
|
||||
throw new ConfigurationException("Multiple resource providers return resource type[" + resourceName + "]: First[" + myTypeToProvider.get(resourceName).getClass().getCanonicalName()
|
||||
+ "] and Second[" + rsrcProvider.getClass().getCanonicalName() + "]");
|
||||
}
|
||||
if (!inInit) {
|
||||
myResourceProviders.add(rsrcProvider);
|
||||
}
|
||||
myTypeToProvider.put(resourceName, rsrcProvider);
|
||||
providedResourceScanner.scanForProvidedResources(rsrcProvider);
|
||||
newResourceProviders.add(rsrcProvider);
|
||||
} else {
|
||||
|
@ -1384,7 +1378,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
|||
|
||||
}
|
||||
if (!newResourceProviders.isEmpty()) {
|
||||
ourLog.info("Added {} resource provider(s). Total {}", newResourceProviders.size(), myTypeToProvider.size());
|
||||
ourLog.info("Added {} resource provider(s). Total {}", newResourceProviders.size(), myResourceProviders.size());
|
||||
for (IResourceProvider provider : newResourceProviders) {
|
||||
assertProviderIsValid(provider);
|
||||
findResourceMethods(provider);
|
||||
|
@ -1594,7 +1588,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
|||
IResourceProvider rsrcProvider = (IResourceProvider) provider;
|
||||
Class<? extends IBaseResource> resourceType = rsrcProvider.getResourceType();
|
||||
String resourceName = getFhirContext().getResourceDefinition(resourceType).getName();
|
||||
myTypeToProvider.remove(resourceName);
|
||||
providedResourceScanner.removeProvidedResources(rsrcProvider);
|
||||
} else {
|
||||
myPlainProviders.remove(provider);
|
||||
|
|
|
@ -20,18 +20,7 @@ package ca.uhn.fhir.rest.server.method;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
|
@ -44,8 +33,22 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
|
|||
import ca.uhn.fhir.rest.param.binder.CollectionBinder;
|
||||
import ca.uhn.fhir.rest.server.method.OperationParameter.IOperationParamConverter;
|
||||
import ca.uhn.fhir.rest.server.method.ResourceParameter.Mode;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.util.ParametersUtil;
|
||||
import ca.uhn.fhir.util.ReflectionUtil;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public class MethodUtil {
|
||||
|
||||
|
@ -90,7 +93,26 @@ public class MethodUtil {
|
|||
}
|
||||
if (Collection.class.isAssignableFrom(parameterType)) {
|
||||
throw new ConfigurationException("Argument #" + paramIndex + " of Method '" + theMethod.getName() + "' in type '" + theMethod.getDeclaringClass().getCanonicalName()
|
||||
+ "' is of an invalid generic type (can not be a collection of a collection of a collection)");
|
||||
+ "' is of an invalid generic type (can not be a collection of a collection of a collection)");
|
||||
}
|
||||
|
||||
/*
|
||||
* If the user is trying to bind IPrimitiveType they are probably
|
||||
* trying to write code that is compatible across versions of FHIR.
|
||||
* We'll try and come up with an appropriate subtype to give
|
||||
* them.
|
||||
*
|
||||
* This gets tested in HistoryR4Test
|
||||
*/
|
||||
if (IPrimitiveType.class.equals(parameterType)) {
|
||||
Class<?> genericType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(theMethod, paramIndex);
|
||||
if (Date.class.equals(genericType)) {
|
||||
BaseRuntimeElementDefinition<?> dateTimeDef = theContext.getElementDefinition("dateTime");
|
||||
parameterType = dateTimeDef.getImplementingClass();
|
||||
} else if (String.class.equals(genericType) || genericType == null) {
|
||||
BaseRuntimeElementDefinition<?> dateTimeDef = theContext.getElementDefinition("string");
|
||||
parameterType = dateTimeDef.getImplementingClass();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,7 +163,7 @@ public class MethodUtil {
|
|||
specType = String.class;
|
||||
} else if ((parameterType != Include.class) || innerCollectionType == null || outerCollectionType != null) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + IncludeParam.class.getSimpleName() + " but has a type other than Collection<"
|
||||
+ Include.class.getSimpleName() + ">");
|
||||
+ Include.class.getSimpleName() + ">");
|
||||
} else {
|
||||
instantiableCollectionType = (Class<? extends Collection<Include>>) CollectionBinder.getInstantiableCollectionType(innerCollectionType, "Method '" + theMethod.getName() + "'");
|
||||
specType = parameterType;
|
||||
|
@ -198,7 +220,7 @@ public class MethodUtil {
|
|||
} else if (nextAnnotation instanceof Validate.Mode) {
|
||||
if (parameterType.equals(ValidationModeEnum.class) == false) {
|
||||
throw new ConfigurationException(
|
||||
"Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Mode.class.getSimpleName() + " must be of type " + ValidationModeEnum.class.getName());
|
||||
"Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Mode.class.getSimpleName() + " must be of type " + ValidationModeEnum.class.getName());
|
||||
}
|
||||
param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_MODE, 0, 1).setConverter(new IOperationParamConverter() {
|
||||
@Override
|
||||
|
@ -221,7 +243,7 @@ public class MethodUtil {
|
|||
} else if (nextAnnotation instanceof Validate.Profile) {
|
||||
if (parameterType.equals(String.class) == false) {
|
||||
throw new ConfigurationException(
|
||||
"Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Profile.class.getSimpleName() + " must be of type " + String.class.getName());
|
||||
"Parameter annotated with @" + Validate.class.getSimpleName() + "." + Validate.Profile.class.getSimpleName() + " must be of type " + String.class.getName());
|
||||
}
|
||||
param = new OperationParameter(theContext, Constants.EXTOP_VALIDATE, Constants.EXTOP_VALIDATE_PROFILE, 0, 1).setConverter(new IOperationParamConverter() {
|
||||
@Override
|
||||
|
@ -244,8 +266,8 @@ public class MethodUtil {
|
|||
|
||||
if (param == null) {
|
||||
throw new ConfigurationException(
|
||||
"Parameter #" + ((paramIndex + 1)) + "/" + (parameterTypes.length) + " of method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName()
|
||||
+ "' has no recognized FHIR interface parameter annotations. Don't know how to handle this parameter");
|
||||
"Parameter #" + ((paramIndex + 1)) + "/" + (parameterTypes.length) + " of method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName()
|
||||
+ "' has no recognized FHIR interface parameter annotations. Don't know how to handle this parameter");
|
||||
}
|
||||
|
||||
param.initializeTypes(theMethod, outerCollectionType, innerCollectionType, parameterType);
|
||||
|
|
|
@ -47,8 +47,8 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
static final String QUALIFIER_ANY_TYPE = ":*";
|
||||
|
||||
static {
|
||||
ourParamTypes = new HashMap<Class<?>, RestSearchParameterTypeEnum>();
|
||||
ourParamQualifiers = new HashMap<RestSearchParameterTypeEnum, Set<String>>();
|
||||
ourParamTypes = new HashMap<>();
|
||||
ourParamQualifiers = new HashMap<>();
|
||||
|
||||
ourParamTypes.put(StringParam.class, RestSearchParameterTypeEnum.STRING);
|
||||
ourParamTypes.put(StringOrListParam.class, RestSearchParameterTypeEnum.STRING);
|
||||
|
@ -124,7 +124,7 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
*/
|
||||
@Override
|
||||
public List<QualifiedParamList> encode(FhirContext theContext, Object theObject) throws InternalErrorException {
|
||||
ArrayList<QualifiedParamList> retVal = new ArrayList<QualifiedParamList>();
|
||||
ArrayList<QualifiedParamList> retVal = new ArrayList<>();
|
||||
|
||||
// TODO: declaring method should probably have a generic type..
|
||||
@SuppressWarnings("rawtypes")
|
||||
|
@ -197,7 +197,7 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
}
|
||||
|
||||
public void setChainlists(String[] theChainWhitelist, String[] theChainBlacklist) {
|
||||
myQualifierWhitelist = new HashSet<String>(theChainWhitelist.length);
|
||||
myQualifierWhitelist = new HashSet<>(theChainWhitelist.length);
|
||||
myQualifierWhitelist.add(QUALIFIER_ANY_TYPE);
|
||||
|
||||
for (int i = 0; i < theChainWhitelist.length; i++) {
|
||||
|
@ -211,7 +211,7 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
}
|
||||
|
||||
if (theChainBlacklist.length > 0) {
|
||||
myQualifierBlacklist = new HashSet<String>(theChainBlacklist.length);
|
||||
myQualifierBlacklist = new HashSet<>(theChainBlacklist.length);
|
||||
for (String next : theChainBlacklist) {
|
||||
if (next.equals(EMPTY_STRING)) {
|
||||
myQualifierBlacklist.add(EMPTY_STRING);
|
||||
|
@ -282,7 +282,7 @@ public class SearchParameter extends BaseQueryParameter {
|
|||
Set<String> builtInQualifiers = ourParamQualifiers.get(typeEnum);
|
||||
if (builtInQualifiers != null) {
|
||||
if (myQualifierWhitelist != null) {
|
||||
HashSet<String> qualifierWhitelist = new HashSet<String>();
|
||||
HashSet<String> qualifierWhitelist = new HashSet<>();
|
||||
qualifierWhitelist.addAll(myQualifierWhitelist);
|
||||
qualifierWhitelist.addAll(builtInQualifiers);
|
||||
myQualifierWhitelist = qualifierWhitelist;
|
||||
|
|
|
@ -20,7 +20,9 @@ package ca.uhn.fhir.rest.server.provider;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.*;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||
|
@ -29,13 +31,16 @@ import ca.uhn.fhir.rest.param.TokenParam;
|
|||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* This class is a simple implementation of the resource provider
|
||||
|
@ -59,6 +64,8 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
private final FhirContext myFhirContext;
|
||||
private final String myResourceName;
|
||||
protected Map<String, TreeMap<Long, T>> myIdToVersionToResourceMap = new HashMap<>();
|
||||
protected Map<String, LinkedList<T>> myIdToHistory = new HashMap<>();
|
||||
protected LinkedList<T> myTypeHistory = new LinkedList<>();
|
||||
private long myNextId;
|
||||
private AtomicLong myDeleteCount = new AtomicLong(0);
|
||||
private AtomicLong mySearchCount = new AtomicLong(0);
|
||||
|
@ -86,6 +93,8 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
public void clear() {
|
||||
myNextId = 1;
|
||||
myIdToVersionToResourceMap.clear();
|
||||
myIdToHistory.clear();
|
||||
myTypeHistory.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -183,6 +192,21 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
return myIdToVersionToResourceMap.get(theIdPart);
|
||||
}
|
||||
|
||||
@History
|
||||
public List<T> historyInstance(@IdParam IIdType theId) {
|
||||
LinkedList<T> retVal = myIdToHistory.get(theId.getIdPart());
|
||||
if (retVal == null) {
|
||||
throw new ResourceNotFoundException(theId);
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@History
|
||||
public List<T> historyType() {
|
||||
return myTypeHistory;
|
||||
}
|
||||
|
||||
@Read(version = true)
|
||||
public IBaseResource read(@IdParam IIdType theId) {
|
||||
TreeMap<Long, T> versions = myIdToVersionToResourceMap.get(theId.getIdPart());
|
||||
|
@ -252,16 +276,52 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
|
|||
|
||||
private IIdType store(@ResourceParam T theResource, String theIdPart, Long theVersionIdPart) {
|
||||
IIdType id = myFhirContext.getVersion().newIdType();
|
||||
id.setParts(null, myResourceName, theIdPart, Long.toString(theVersionIdPart));
|
||||
String versionIdPart = Long.toString(theVersionIdPart);
|
||||
id.setParts(null, myResourceName, theIdPart, versionIdPart);
|
||||
if (theResource != null) {
|
||||
theResource.setId(id);
|
||||
}
|
||||
|
||||
TreeMap<Long, T> versionToResource = getVersionToResource(theIdPart);
|
||||
versionToResource.put(theVersionIdPart, theResource);
|
||||
/*
|
||||
* This is a bit of magic to make sure that the versionId attribute
|
||||
* in the resource being stored accurately represents the version
|
||||
* that was assigned by this provider
|
||||
*/
|
||||
if (theResource != null) {
|
||||
if (myFhirContext.getVersion().getVersion() == FhirVersionEnum.DSTU2) {
|
||||
ResourceMetadataKeyEnum.VERSION.put((IResource) theResource, versionIdPart);
|
||||
} else {
|
||||
BaseRuntimeChildDefinition metaChild = myFhirContext.getResourceDefinition(myResourceType).getChildByName("meta");
|
||||
List<IBase> metaValues = metaChild.getAccessor().getValues(theResource);
|
||||
if (metaValues.size() > 0) {
|
||||
IBase meta = metaValues.get(0);
|
||||
BaseRuntimeElementCompositeDefinition<?> metaDef = (BaseRuntimeElementCompositeDefinition<?>) myFhirContext.getElementDefinition(meta.getClass());
|
||||
BaseRuntimeChildDefinition versionIdDef = metaDef.getChildByName("versionId");
|
||||
List<IBase> versionIdValues = versionIdDef.getAccessor().getValues(meta);
|
||||
if (versionIdValues.size() > 0) {
|
||||
IPrimitiveType<?> versionId = (IPrimitiveType<?>) versionIdValues.get(0);
|
||||
versionId.setValueAsString(versionIdPart);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ourLog.info("Storing resource with ID: {}", id.getValue());
|
||||
|
||||
// Store to ID->version->resource map
|
||||
TreeMap<Long, T> versionToResource = getVersionToResource(theIdPart);
|
||||
versionToResource.put(theVersionIdPart, theResource);
|
||||
|
||||
// Store to type history map
|
||||
myTypeHistory.addFirst(theResource);
|
||||
|
||||
// Store to ID history map
|
||||
if (!myIdToHistory.containsKey(theIdPart)) {
|
||||
myIdToHistory.put(theIdPart, new LinkedList<>());
|
||||
}
|
||||
myIdToHistory.get(theIdPart).addFirst(theResource);
|
||||
|
||||
// Return the newly assigned ID including the version ID
|
||||
return id;
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -230,12 +230,14 @@ public class CreateR4Test {
|
|||
ourPort = PortUtil.findFreePort();
|
||||
ourServer = new Server(ourPort);
|
||||
|
||||
PatientProvider patientProvider = new PatientProvider();
|
||||
PatientProviderCreate patientProviderCreate = new PatientProviderCreate();
|
||||
PatientProviderRead patientProviderRead = new PatientProviderRead();
|
||||
PatientProviderSearch patientProviderSearch = new PatientProviderSearch();
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
RestfulServer servlet = new RestfulServer(ourCtx);
|
||||
|
||||
servlet.setResourceProviders(patientProvider);
|
||||
servlet.setResourceProviders(patientProviderCreate, patientProviderRead, patientProviderSearch);
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||
ourServer.setHandler(proxyHandler);
|
||||
|
@ -247,19 +249,7 @@ public class CreateR4Test {
|
|||
ourClient = builder.build();
|
||||
|
||||
}
|
||||
|
||||
public static class PatientProvider implements IResourceProvider {
|
||||
|
||||
@Create()
|
||||
public MethodOutcome create(@ResourceParam Patient theIdParam) {
|
||||
assertNull(theIdParam.getIdElement().getIdPart());
|
||||
return new MethodOutcome(new IdType("Patient", "1"), true).setOperationOutcome(ourReturnOo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Patient> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
public static class PatientProviderRead implements IResourceProvider {
|
||||
|
||||
@Read()
|
||||
public MyPatientWithExtensions read(@IdParam IdType theIdParam) {
|
||||
|
@ -269,6 +259,33 @@ public class CreateR4Test {
|
|||
return p0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Patient> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PatientProviderCreate implements IResourceProvider {
|
||||
@Override
|
||||
public Class<Patient> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
@Create()
|
||||
public MethodOutcome create(@ResourceParam Patient theIdParam) {
|
||||
assertNull(theIdParam.getIdElement().getIdPart());
|
||||
return new MethodOutcome(new IdType("Patient", "1"), true).setOperationOutcome(ourReturnOo);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PatientProviderSearch implements IResourceProvider {
|
||||
|
||||
|
||||
@Override
|
||||
public Class<Patient> getResourceType() {
|
||||
return Patient.class;
|
||||
}
|
||||
|
||||
|
||||
@Search
|
||||
public List<IBaseResource> search() {
|
||||
ArrayList<IBaseResource> retVal = new ArrayList<IBaseResource>();
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
|
@ -15,65 +16,53 @@ 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.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.rest.annotation.At;
|
||||
import ca.uhn.fhir.rest.annotation.History;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.Read;
|
||||
import ca.uhn.fhir.rest.annotation.Since;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class HistoryDstu2Test {
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class HistoryR4Test {
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(HistoryR4Test.class);
|
||||
private static CloseableHttpClient ourClient;
|
||||
private static FhirContext ourCtx = FhirContext.forDstu2();
|
||||
private static FhirContext ourCtx = FhirContext.forR4();
|
||||
private static DateRangeParam ourLastAt;
|
||||
|
||||
private static InstantDt ourLastSince;
|
||||
private static InstantType ourLastSince;
|
||||
private static IPrimitiveType<Date> ourLastSince2;
|
||||
private static IPrimitiveType<String> ourLastSince3;
|
||||
private static IPrimitiveType<?> ourLastSince4;
|
||||
private static int ourPort;
|
||||
|
||||
private static Server ourServer;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
ourLastAt = null;
|
||||
ourLastSince = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSince() throws Exception {
|
||||
{
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/_history?_since=2005");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
|
||||
assertEquals(null, ourLastAt);
|
||||
assertEquals("2005", ourLastSince.getValueAsString());
|
||||
}
|
||||
ourLastSince2 = null;
|
||||
ourLastSince3 = null;
|
||||
ourLastSince4 = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAt() throws Exception {
|
||||
{
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/_history?_at=gt2001&_at=lt2005");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
assertEquals(ParamPrefixEnum.GREATERTHAN, ourLastAt.getLowerBound().getPrefix());
|
||||
assertEquals("2001", ourLastAt.getLowerBound().getValueAsString());
|
||||
|
@ -86,56 +75,79 @@ public class HistoryDstu2Test {
|
|||
public void testInstanceHistory() throws Exception {
|
||||
{
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/_history?_pretty=true");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
||||
ourLog.info(responseContent);
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
String responseContent;
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
Bundle bundle = ourCtx.newXmlParser().parseResource(Bundle.class, responseContent);
|
||||
assertEquals(2, bundle.getEntry().size());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/ih1/_history/1", bundle.getEntry().get(0).getResource().getId().getValue());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/ih1/_history/2", bundle.getEntry().get(1).getResource().getId().getValue());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/ih1/_history/1", bundle.getEntry().get(0).getResource().getId());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/ih1/_history/2", bundle.getEntry().get(1).getResource().getId());
|
||||
|
||||
}
|
||||
}private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(HistoryDstu2Test.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerHistory() throws Exception {
|
||||
{
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/_history");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
String responseContent;
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
Bundle bundle = ourCtx.newXmlParser().parseResource(Bundle.class, responseContent);
|
||||
assertEquals(2, bundle.getEntry().size());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/h1/_history/1", bundle.getEntry().get(0).getResource().getId().getValue());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/h1/_history/2", bundle.getEntry().get(1).getResource().getId().getValue());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/h1/_history/1", bundle.getEntry().get(0).getResource().getId());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/h1/_history/2", bundle.getEntry().get(1).getResource().getId());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSince() throws Exception {
|
||||
{
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/_history?_since=2005");
|
||||
String responseContent;
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
assertEquals(null, ourLastAt);
|
||||
assertEquals("2005", ourLastSince.getValueAsString());
|
||||
assertEquals("2005", ourLastSince2.getValueAsString());
|
||||
assertTrue(DateTimeType.class.equals(ourLastSince2.getClass()));
|
||||
assertEquals("2005", ourLastSince3.getValueAsString());
|
||||
assertTrue(StringType.class.equals(ourLastSince3.getClass()));
|
||||
assertEquals("2005", ourLastSince4.getValueAsString());
|
||||
assertTrue(StringType.class.equals(ourLastSince4.getClass()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTypeHistory() throws Exception {
|
||||
{
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/_history");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
String responseContent;
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
assertNull(ourLastAt);
|
||||
|
||||
Bundle bundle = ourCtx.newXmlParser().parseResource(Bundle.class, responseContent);
|
||||
assertEquals(2, bundle.getEntry().size());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/th1/_history/1", bundle.getEntry().get(0).getResource().getId().getValue());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/th1/_history/2", bundle.getEntry().get(1).getResource().getId().getValue());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/th1/_history/1", bundle.getEntry().get(0).getResource().getId());
|
||||
assertEquals("http://localhost:" + ourPort + "/Patient/th1/_history/2", bundle.getEntry().get(1).getResource().getId());
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -147,14 +159,15 @@ public class HistoryDstu2Test {
|
|||
public void testVread() throws Exception {
|
||||
{
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/123/_history/456");
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
String responseContent;
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info(responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
Patient bundle = ourCtx.newXmlParser().parseResource(Patient.class, responseContent);
|
||||
assertEquals("vread", bundle.getNameFirstRep().getFamilyFirstRep().getValue());
|
||||
assertEquals("vread", bundle.getNameFirstRep().getFamily());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,20 +204,27 @@ public class HistoryDstu2Test {
|
|||
public static class DummyPlainProvider {
|
||||
|
||||
@History
|
||||
public List<Patient> history(@Since InstantDt theSince, @At DateRangeParam theAt) {
|
||||
public List<Patient> history(@Since InstantType theSince,
|
||||
@Since IPrimitiveType<Date> theSince2,
|
||||
@Since IPrimitiveType<String> theSince3,
|
||||
@Since IPrimitiveType theSince4,
|
||||
@At DateRangeParam theAt) {
|
||||
ourLastAt = theAt;
|
||||
ourLastSince = theSince;
|
||||
ourLastSince2 = theSince2;
|
||||
ourLastSince3 = theSince3;
|
||||
ourLastSince4 = theSince4;
|
||||
|
||||
ArrayList<Patient> retVal = new ArrayList<Patient>();
|
||||
ArrayList<Patient> retVal = new ArrayList<>();
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/h1/_history/1");
|
||||
patient.addName().addFamily("history");
|
||||
patient.addName().setFamily("history");
|
||||
retVal.add(patient);
|
||||
|
||||
Patient patient2 = new Patient();
|
||||
patient2.setId("Patient/h1/_history/2");
|
||||
patient2.addName().addFamily("history");
|
||||
patient2.addName().setFamily("history");
|
||||
retVal.add(patient2);
|
||||
|
||||
return retVal;
|
||||
|
@ -220,17 +240,17 @@ public class HistoryDstu2Test {
|
|||
}
|
||||
|
||||
@History
|
||||
public List<Patient> instanceHistory(@IdParam IdDt theId) {
|
||||
ArrayList<Patient> retVal = new ArrayList<Patient>();
|
||||
public List<Patient> instanceHistory(@IdParam IdType theId) {
|
||||
ArrayList<Patient> retVal = new ArrayList<>();
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/ih1/_history/1");
|
||||
patient.addName().addFamily("history");
|
||||
patient.addName().setFamily("history");
|
||||
retVal.add(patient);
|
||||
|
||||
Patient patient2 = new Patient();
|
||||
patient2.setId("Patient/ih1/_history/2");
|
||||
patient2.addName().addFamily("history");
|
||||
patient2.addName().setFamily("history");
|
||||
retVal.add(patient2);
|
||||
|
||||
return retVal;
|
||||
|
@ -238,25 +258,25 @@ public class HistoryDstu2Test {
|
|||
|
||||
@History
|
||||
public List<Patient> typeHistory() {
|
||||
ArrayList<Patient> retVal = new ArrayList<Patient>();
|
||||
ArrayList<Patient> retVal = new ArrayList<>();
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/th1/_history/1");
|
||||
patient.addName().addFamily("history");
|
||||
patient.addName().setFamily("history");
|
||||
retVal.add(patient);
|
||||
|
||||
Patient patient2 = new Patient();
|
||||
patient2.setId("Patient/th1/_history/2");
|
||||
patient2.addName().addFamily("history");
|
||||
patient2.addName().setFamily("history");
|
||||
retVal.add(patient2);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Read(version = true)
|
||||
public Patient vread(@IdParam IdDt theId) {
|
||||
public Patient vread(@IdParam IdType theId) {
|
||||
Patient retVal = new Patient();
|
||||
retVal.addName().addFamily("vread");
|
||||
retVal.addName().setFamily("vread");
|
||||
retVal.setId(theId);
|
||||
return retVal;
|
||||
}
|
|
@ -2,6 +2,7 @@ package ca.uhn.fhir.rest.server.provider;
|
|||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
|
@ -19,17 +20,19 @@ import org.junit.AfterClass;
|
|||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.matchesPattern;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class HashMapResourceProviderTest {
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(HashMapResourceProviderTest.class);
|
||||
private static MyRestfulServer ourRestServer;
|
||||
private static Server ourListenerServer;
|
||||
private static IGenericClient ourClient;
|
||||
|
@ -100,6 +103,93 @@ public class HashMapResourceProviderTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHistoryInstance() {
|
||||
// Create Res 1
|
||||
Patient p = new Patient();
|
||||
p.setActive(true);
|
||||
IIdType id1 = ourClient.create().resource(p).execute().getId();
|
||||
assertThat(id1.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("1", id1.getVersionIdPart());
|
||||
|
||||
// Create Res 2
|
||||
p = new Patient();
|
||||
p.setActive(true);
|
||||
IIdType id2 = ourClient.create().resource(p).execute().getId();
|
||||
assertThat(id2.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("1", id2.getVersionIdPart());
|
||||
|
||||
// Update Res 2
|
||||
p = new Patient();
|
||||
p.setId(id2);
|
||||
p.setActive(false);
|
||||
id2 = ourClient.update().resource(p).execute().getId();
|
||||
assertThat(id2.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("2", id2.getVersionIdPart());
|
||||
|
||||
Bundle history = ourClient
|
||||
.history()
|
||||
.onInstance(id2.toUnqualifiedVersionless())
|
||||
.andReturnBundle(Bundle.class)
|
||||
.encodedJson()
|
||||
.prettyPrint()
|
||||
.execute();
|
||||
ourLog.debug(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(history));
|
||||
List<String> ids = history
|
||||
.getEntry()
|
||||
.stream()
|
||||
.map(t -> t.getResource().getIdElement().toUnqualified().getValue())
|
||||
.collect(Collectors.toList());
|
||||
assertThat(ids, contains(
|
||||
id2.toUnqualified().withVersion("2").getValue(),
|
||||
id2.toUnqualified().withVersion("1").getValue()
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHistoryType() {
|
||||
// Create Res 1
|
||||
Patient p = new Patient();
|
||||
p.setActive(true);
|
||||
IIdType id1 = ourClient.create().resource(p).execute().getId();
|
||||
assertThat(id1.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("1", id1.getVersionIdPart());
|
||||
|
||||
// Create Res 2
|
||||
p = new Patient();
|
||||
p.setActive(true);
|
||||
IIdType id2 = ourClient.create().resource(p).execute().getId();
|
||||
assertThat(id2.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("1", id2.getVersionIdPart());
|
||||
|
||||
// Update Res 2
|
||||
p = new Patient();
|
||||
p.setId(id2);
|
||||
p.setActive(false);
|
||||
id2 = ourClient.update().resource(p).execute().getId();
|
||||
assertThat(id2.getIdPart(), matchesPattern("[0-9]+"));
|
||||
assertEquals("2", id2.getVersionIdPart());
|
||||
|
||||
Bundle history = ourClient
|
||||
.history()
|
||||
.onType(Patient.class)
|
||||
.andReturnBundle(Bundle.class)
|
||||
.execute();
|
||||
List<String> ids = history
|
||||
.getEntry()
|
||||
.stream()
|
||||
.map(t -> t.getResource().getIdElement().toUnqualified().getValue())
|
||||
.collect(Collectors.toList());
|
||||
ourLog.info("Received IDs: {}", ids);
|
||||
assertThat(ids, contains(
|
||||
id2.toUnqualified().withVersion("2").getValue(),
|
||||
id2.toUnqualified().withVersion("1").getValue(),
|
||||
id1.toUnqualified().withVersion("1").getValue()
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchAll() {
|
||||
// Create
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
Resource loading logic for the JPA server has been optimized to
|
||||
reduce the number of database round trips required when loading
|
||||
search results where many of the entries have a "forced ID" (an alphanumeric
|
||||
client-assigned reosurce ID). Thanks to Frank Tao for the pull
|
||||
client-assigned resource ID). Thanks to Frank Tao for the pull
|
||||
request!
|
||||
</action>
|
||||
<action type="add">
|
||||
|
@ -150,6 +150,39 @@
|
|||
in order to prevent HTML injection attacks via maliciously
|
||||
crafted URLs.
|
||||
</action>
|
||||
<action type="add">
|
||||
The REST Server module now allows more than one Resource Provider
|
||||
(i.e. more than one implementation of IResourceProvider) to be registered
|
||||
to the RestfulServer for the same resource type. Previous versions of
|
||||
HAPI FHIR have always limited support to a single resource provider, but
|
||||
this limitation did not serve any purpose so it has been removed.
|
||||
</action>
|
||||
<action type="add">
|
||||
The HashMapResourceProvider now supports the type and
|
||||
instance history operations.
|
||||
</action>
|
||||
<action type="fix">
|
||||
Fixed a bug when creating a custom search parameter in the JPA
|
||||
server: if the SearchParameter resource contained an invalid
|
||||
expression, create/update operations for the given resource would
|
||||
fail with a cryptic error. SearchParameter expressions are now
|
||||
validated upon storage, and the SearchParameter will be rejected
|
||||
if the expression can not be processed.
|
||||
</action>
|
||||
<action type="add">
|
||||
The generic client history operations (history-instance, history-type,
|
||||
and history-server) now support the
|
||||
<![CDATA[<code>_at</code>]]> parameter.
|
||||
</action>
|
||||
<action type="add">
|
||||
In the plain server, many resource provider method parameters may now
|
||||
use a generic
|
||||
<![CDATA[<code>IPrimitiveType<String></code>]]>
|
||||
or
|
||||
<![CDATA[<code>IPrimitiveType<Date></code>]]> at the
|
||||
parameter type. This is handy if you are trying to write code
|
||||
that works across versions of FHIR.
|
||||
</action>
|
||||
</release>
|
||||
<release version="3.4.0" date="2018-05-28">
|
||||
<action type="add">
|
||||
|
|
Loading…
Reference in New Issue