Merge branch 'master' into fhirterser-getvalues-enhancements
This commit is contained in:
commit
4cd86596f2
|
@ -20,15 +20,14 @@ package ca.uhn.fhir.rest.annotation;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RESTful method annotation used for a method which provides FHIR "operations".
|
* RESTful method annotation used for a method which provides FHIR "operations".
|
||||||
*/
|
*/
|
||||||
|
@ -36,6 +35,14 @@ import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||||
@Target(value = ElementType.METHOD)
|
@Target(value = ElementType.METHOD)
|
||||||
public @interface Operation {
|
public @interface Operation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constant is a special return value for {@link #name()}. If this name is
|
||||||
|
* used, the given operation method will match all operation calls. This is
|
||||||
|
* generally not desirable, but can be useful if you have a server that should
|
||||||
|
* dynamically match any FHIR operations that are requested.
|
||||||
|
*/
|
||||||
|
String NAME_MATCH_ALL = "*";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the operation, e.g. "<code>$everything</code>"
|
* The name of the operation, e.g. "<code>$everything</code>"
|
||||||
*
|
*
|
||||||
|
|
|
@ -31,7 +31,7 @@ import java.util.List;
|
||||||
* @author james
|
* @author james
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class StringClientParam extends BaseClientParam implements IParam {
|
public class StringClientParam extends BaseClientParam implements IParam {
|
||||||
|
|
||||||
private final String myParamName;
|
private final String myParamName;
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,7 @@ public class TestUtil {
|
||||||
* environment
|
* environment
|
||||||
*/
|
*/
|
||||||
public static void randomizeLocale() {
|
public static void randomizeLocale() {
|
||||||
Locale[] availableLocales = {Locale.CANADA, Locale.GERMANY, Locale.TAIWAN};
|
Locale[] availableLocales = {Locale.CANADA, Locale.GERMANY, Locale.TAIWAN};
|
||||||
Locale.setDefault(availableLocales[(int) (Math.random() * availableLocales.length)]);
|
Locale.setDefault(availableLocales[(int) (Math.random() * availableLocales.length)]);
|
||||||
ourLog.info("Tests are running in locale: " + Locale.getDefault().getDisplayName());
|
ourLog.info("Tests are running in locale: " + Locale.getDefault().getDisplayName());
|
||||||
if (Math.random() < 0.5) {
|
if (Math.random() < 0.5) {
|
||||||
|
|
|
@ -2086,6 +2086,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
*/
|
*/
|
||||||
if (thePerformIndexing) {
|
if (thePerformIndexing) {
|
||||||
|
|
||||||
|
calculateHashes(stringParams);
|
||||||
for (ResourceIndexedSearchParamString next : removeCommon(existingStringParams, stringParams)) {
|
for (ResourceIndexedSearchParamString next : removeCommon(existingStringParams, stringParams)) {
|
||||||
next.setDaoConfig(myConfig);
|
next.setDaoConfig(myConfig);
|
||||||
myEntityManager.remove(next);
|
myEntityManager.remove(next);
|
||||||
|
@ -2095,6 +2096,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
myEntityManager.persist(next);
|
myEntityManager.persist(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
calculateHashes(tokenParams);
|
||||||
for (ResourceIndexedSearchParamToken next : removeCommon(existingTokenParams, tokenParams)) {
|
for (ResourceIndexedSearchParamToken next : removeCommon(existingTokenParams, tokenParams)) {
|
||||||
myEntityManager.remove(next);
|
myEntityManager.remove(next);
|
||||||
theEntity.getParamsToken().remove(next);
|
theEntity.getParamsToken().remove(next);
|
||||||
|
@ -2103,6 +2105,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
myEntityManager.persist(next);
|
myEntityManager.persist(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
calculateHashes(numberParams);
|
||||||
for (ResourceIndexedSearchParamNumber next : removeCommon(existingNumberParams, numberParams)) {
|
for (ResourceIndexedSearchParamNumber next : removeCommon(existingNumberParams, numberParams)) {
|
||||||
myEntityManager.remove(next);
|
myEntityManager.remove(next);
|
||||||
theEntity.getParamsNumber().remove(next);
|
theEntity.getParamsNumber().remove(next);
|
||||||
|
@ -2111,6 +2114,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
myEntityManager.persist(next);
|
myEntityManager.persist(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
calculateHashes(quantityParams);
|
||||||
for (ResourceIndexedSearchParamQuantity next : removeCommon(existingQuantityParams, quantityParams)) {
|
for (ResourceIndexedSearchParamQuantity next : removeCommon(existingQuantityParams, quantityParams)) {
|
||||||
myEntityManager.remove(next);
|
myEntityManager.remove(next);
|
||||||
theEntity.getParamsQuantity().remove(next);
|
theEntity.getParamsQuantity().remove(next);
|
||||||
|
@ -2120,6 +2124,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store date SP's
|
// Store date SP's
|
||||||
|
calculateHashes(dateParams);
|
||||||
for (ResourceIndexedSearchParamDate next : removeCommon(existingDateParams, dateParams)) {
|
for (ResourceIndexedSearchParamDate next : removeCommon(existingDateParams, dateParams)) {
|
||||||
myEntityManager.remove(next);
|
myEntityManager.remove(next);
|
||||||
theEntity.getParamsDate().remove(next);
|
theEntity.getParamsDate().remove(next);
|
||||||
|
@ -2129,6 +2134,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store URI SP's
|
// Store URI SP's
|
||||||
|
calculateHashes(uriParams);
|
||||||
for (ResourceIndexedSearchParamUri next : removeCommon(existingUriParams, uriParams)) {
|
for (ResourceIndexedSearchParamUri next : removeCommon(existingUriParams, uriParams)) {
|
||||||
myEntityManager.remove(next);
|
myEntityManager.remove(next);
|
||||||
theEntity.getParamsUri().remove(next);
|
theEntity.getParamsUri().remove(next);
|
||||||
|
@ -2138,6 +2144,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store Coords SP's
|
// Store Coords SP's
|
||||||
|
calculateHashes(coordsParams);
|
||||||
for (ResourceIndexedSearchParamCoords next : removeCommon(existingCoordsParams, coordsParams)) {
|
for (ResourceIndexedSearchParamCoords next : removeCommon(existingCoordsParams, coordsParams)) {
|
||||||
myEntityManager.remove(next);
|
myEntityManager.remove(next);
|
||||||
theEntity.getParamsCoords().remove(next);
|
theEntity.getParamsCoords().remove(next);
|
||||||
|
@ -2187,6 +2194,12 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
||||||
return theEntity;
|
return theEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void calculateHashes(Collection<? extends BaseResourceIndexedSearchParam> theStringParams) {
|
||||||
|
for (BaseResourceIndexedSearchParam next : theStringParams) {
|
||||||
|
next.calculateHashes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, ResourceTable
|
protected ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, ResourceTable
|
||||||
entity, Date theDeletedTimestampOrNull, Date theUpdateTime) {
|
entity, Date theDeletedTimestampOrNull, Date theUpdateTime) {
|
||||||
return updateEntity(theRequest, theResource, entity, theDeletedTimestampOrNull, true, true, theUpdateTime, false, true);
|
return updateEntity(theRequest, theResource, entity, theDeletedTimestampOrNull, true, true, theUpdateTime, false, true);
|
||||||
|
|
|
@ -155,6 +155,7 @@ public class DaoConfig {
|
||||||
private boolean myValidateSearchParameterExpressionsOnSave = true;
|
private boolean myValidateSearchParameterExpressionsOnSave = true;
|
||||||
private List<Integer> mySearchPreFetchThresholds = Arrays.asList(500, 2000, -1);
|
private List<Integer> mySearchPreFetchThresholds = Arrays.asList(500, 2000, -1);
|
||||||
private List<WarmCacheEntry> myWarmCacheEntries = new ArrayList<>();
|
private List<WarmCacheEntry> myWarmCacheEntries = new ArrayList<>();
|
||||||
|
private boolean myDisableHashBasedSearches;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -1383,6 +1384,34 @@ public class DaoConfig {
|
||||||
return mySearchPreFetchThresholds;
|
return mySearchPreFetchThresholds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to <code>true</code> (default is false) the server will not use
|
||||||
|
* hash based searches. These searches were introduced in HAPI FHIR 3.5.0
|
||||||
|
* and are the new default way of searching. However they require a very
|
||||||
|
* large data migration if an existing system has a large amount of data
|
||||||
|
* so this setting can be used to use the old search mechanism while data
|
||||||
|
* is migrated.
|
||||||
|
*
|
||||||
|
* @since 3.6.0
|
||||||
|
*/
|
||||||
|
public boolean getDisableHashBasedSearches() {
|
||||||
|
return myDisableHashBasedSearches;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to <code>true</code> (default is false) the server will not use
|
||||||
|
* hash based searches. These searches were introduced in HAPI FHIR 3.5.0
|
||||||
|
* and are the new default way of searching. However they require a very
|
||||||
|
* large data migration if an existing system has a large amount of data
|
||||||
|
* so this setting can be used to use the old search mechanism while data
|
||||||
|
* is migrated.
|
||||||
|
*
|
||||||
|
* @since 3.6.0
|
||||||
|
*/
|
||||||
|
public void setDisableHashBasedSearches(boolean theDisableHashBasedSearches) {
|
||||||
|
myDisableHashBasedSearches = theDisableHashBasedSearches;
|
||||||
|
}
|
||||||
|
|
||||||
public enum IndexEnabledEnum {
|
public enum IndexEnabledEnum {
|
||||||
ENABLED,
|
ENABLED,
|
||||||
DISABLED
|
DISABLED
|
||||||
|
|
|
@ -55,6 +55,7 @@ import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.apache.commons.lang3.builder.EqualsBuilder;
|
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||||
|
@ -94,6 +95,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
private static SearchParameterMap ourLastHandlerParamsForUnitTest;
|
private static SearchParameterMap ourLastHandlerParamsForUnitTest;
|
||||||
private static String ourLastHandlerThreadForUnitTest;
|
private static String ourLastHandlerThreadForUnitTest;
|
||||||
private static boolean ourTrackHandlersForUnitTest;
|
private static boolean ourTrackHandlersForUnitTest;
|
||||||
|
private final boolean myDontUseHashesForSearch;
|
||||||
protected IResourceTagDao myResourceTagDao;
|
protected IResourceTagDao myResourceTagDao;
|
||||||
private IResourceSearchViewDao myResourceSearchViewDao;
|
private IResourceSearchViewDao myResourceSearchViewDao;
|
||||||
private List<Long> myAlsoIncludePids;
|
private List<Long> myAlsoIncludePids;
|
||||||
|
@ -130,6 +132,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
myEntityManager = theEntityManager;
|
myEntityManager = theEntityManager;
|
||||||
myFulltextSearchSvc = theFulltextSearchSvc;
|
myFulltextSearchSvc = theFulltextSearchSvc;
|
||||||
myCallingDao = theDao;
|
myCallingDao = theDao;
|
||||||
|
myDontUseHashesForSearch = theDao.getConfig().getDisableHashBasedSearches();
|
||||||
myResourceIndexedSearchParamUriDao = theResourceIndexedSearchParamUriDao;
|
myResourceIndexedSearchParamUriDao = theResourceIndexedSearchParamUriDao;
|
||||||
myForcedIdDao = theForcedIdDao;
|
myForcedIdDao = theForcedIdDao;
|
||||||
myTerminologySvc = theTerminologySvc;
|
myTerminologySvc = theTerminologySvc;
|
||||||
|
@ -304,6 +307,15 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing) {
|
private void addPredicateParamMissing(String theResourceName, String theParamName, boolean theMissing) {
|
||||||
|
// if (myDontUseHashesForSearch) {
|
||||||
|
// Join<ResourceTable, SearchParamPresent> paramPresentJoin = myResourceTableRoot.join("mySearchParamPresents", JoinType.LEFT);
|
||||||
|
// Join<Object, Object> paramJoin = paramPresentJoin.join("mySearchParam", JoinType.LEFT);
|
||||||
|
//
|
||||||
|
// myPredicates.add(myBuilder.equal(paramJoin.get("myResourceName"), theResourceName));
|
||||||
|
// myPredicates.add(myBuilder.equal(paramJoin.get("myParamName"), theParamName));
|
||||||
|
// myPredicates.add(myBuilder.equal(paramPresentJoin.get("myPresent"), !theMissing));
|
||||||
|
// }
|
||||||
|
|
||||||
Join<ResourceTable, SearchParamPresent> paramPresentJoin = myResourceTableRoot.join("mySearchParamPresents", JoinType.LEFT);
|
Join<ResourceTable, SearchParamPresent> paramPresentJoin = myResourceTableRoot.join("mySearchParamPresents", JoinType.LEFT);
|
||||||
|
|
||||||
Expression<Long> hashPresence = paramPresentJoin.get("myHashPresence").as(Long.class);
|
Expression<Long> hashPresence = paramPresentJoin.get("myHashPresence").as(Long.class);
|
||||||
|
@ -841,10 +853,18 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
long hashUri = ResourceIndexedSearchParamUri.calculateHashUri(theResourceName, theParamName, value);
|
if (myDontUseHashesForSearch) {
|
||||||
Predicate hashPredicate = myBuilder.equal(join.get("myHashUri"), hashUri);
|
|
||||||
codePredicates.add(hashPredicate);
|
|
||||||
|
|
||||||
|
Predicate predicate = myBuilder.equal(join.get("myUri").as(String.class), value);
|
||||||
|
codePredicates.add(predicate);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
long hashUri = ResourceIndexedSearchParamUri.calculateHashUri(theResourceName, theParamName, value);
|
||||||
|
Predicate hashPredicate = myBuilder.equal(join.get("myHashUri"), hashUri);
|
||||||
|
codePredicates.add(hashPredicate);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -868,6 +888,13 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Predicate combineParamIndexPredicateWithParamNamePredicate(String theResourceName, String theParamName, From<?, ? extends BaseResourceIndexedSearchParam> theFrom, Predicate thePredicate) {
|
private Predicate combineParamIndexPredicateWithParamNamePredicate(String theResourceName, String theParamName, From<?, ? extends BaseResourceIndexedSearchParam> theFrom, Predicate thePredicate) {
|
||||||
|
if (myDontUseHashesForSearch) {
|
||||||
|
Predicate resourceTypePredicate = myBuilder.equal(theFrom.get("myResourceType"), theResourceName);
|
||||||
|
Predicate paramNamePredicate = myBuilder.equal(theFrom.get("myParamName"), theParamName);
|
||||||
|
Predicate outerPredicate = myBuilder.and(resourceTypePredicate, paramNamePredicate, thePredicate);
|
||||||
|
return outerPredicate;
|
||||||
|
}
|
||||||
|
|
||||||
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(theResourceName, theParamName);
|
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(theResourceName, theParamName);
|
||||||
Predicate hashIdentityPredicate = myBuilder.equal(theFrom.get("myHashIdentity"), hashIdentity);
|
Predicate hashIdentityPredicate = myBuilder.equal(theFrom.get("myHashIdentity"), hashIdentity);
|
||||||
return myBuilder.and(hashIdentityPredicate, thePredicate);
|
return myBuilder.and(hashIdentityPredicate, thePredicate);
|
||||||
|
@ -1079,6 +1106,37 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
throw new IllegalArgumentException("Invalid quantity type: " + theParam.getClass());
|
throw new IllegalArgumentException("Invalid quantity type: " + theParam.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (myDontUseHashesForSearch) {
|
||||||
|
Predicate system = null;
|
||||||
|
if (!isBlank(systemValue)) {
|
||||||
|
system = theBuilder.equal(theFrom.get("mySystem"), systemValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
Predicate code = null;
|
||||||
|
if (!isBlank(unitsValue)) {
|
||||||
|
code = theBuilder.equal(theFrom.get("myUnits"), unitsValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmpValue = ObjectUtils.defaultIfNull(cmpValue, ParamPrefixEnum.EQUAL);
|
||||||
|
final Expression<BigDecimal> path = theFrom.get("myValue");
|
||||||
|
String invalidMessageName = "invalidQuantityPrefix";
|
||||||
|
|
||||||
|
Predicate num = createPredicateNumeric(theResourceName, null, theFrom, theBuilder, theParam, cmpValue, valueValue, path, invalidMessageName);
|
||||||
|
|
||||||
|
Predicate singleCode;
|
||||||
|
if (system == null && code == null) {
|
||||||
|
singleCode = num;
|
||||||
|
} else if (system == null) {
|
||||||
|
singleCode = theBuilder.and(code, num);
|
||||||
|
} else if (code == null) {
|
||||||
|
singleCode = theBuilder.and(system, num);
|
||||||
|
} else {
|
||||||
|
singleCode = theBuilder.and(system, code, num);
|
||||||
|
}
|
||||||
|
|
||||||
|
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
|
||||||
|
}
|
||||||
|
|
||||||
Predicate hashPredicate;
|
Predicate hashPredicate;
|
||||||
if (!isBlank(systemValue) && !isBlank(unitsValue)) {
|
if (!isBlank(systemValue) && !isBlank(unitsValue)) {
|
||||||
long hash = ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(theResourceName, theParamName, systemValue, unitsValue);
|
long hash = ResourceIndexedSearchParamQuantity.calculateHashSystemAndUnits(theResourceName, theParamName, systemValue, unitsValue);
|
||||||
|
@ -1130,6 +1188,31 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
+ ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm);
|
+ ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (myDontUseHashesForSearch) {
|
||||||
|
String likeExpression = BaseHapiFhirDao.normalizeString(rawSearchTerm);
|
||||||
|
if (myCallingDao.getConfig().isAllowContainsSearches()) {
|
||||||
|
if (theParameter instanceof StringParam) {
|
||||||
|
if (((StringParam) theParameter).isContains()) {
|
||||||
|
likeExpression = createLeftAndRightMatchLikeExpression(likeExpression);
|
||||||
|
} else {
|
||||||
|
likeExpression = createLeftMatchLikeExpression(likeExpression);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
likeExpression = createLeftMatchLikeExpression(likeExpression);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
likeExpression = createLeftMatchLikeExpression(likeExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression);
|
||||||
|
if (theParameter instanceof StringParam && ((StringParam) theParameter).isExact()) {
|
||||||
|
Predicate exactCode = theBuilder.equal(theFrom.get("myValueExact"), rawSearchTerm);
|
||||||
|
singleCode = theBuilder.and(singleCode, exactCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
|
||||||
|
}
|
||||||
|
|
||||||
boolean exactMatch = theParameter instanceof StringParam && ((StringParam) theParameter).isExact();
|
boolean exactMatch = theParameter instanceof StringParam && ((StringParam) theParameter).isExact();
|
||||||
if (exactMatch) {
|
if (exactMatch) {
|
||||||
|
|
||||||
|
@ -1234,6 +1317,92 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
return new BooleanStaticAssertionPredicate((CriteriaBuilderImpl) theBuilder, false);
|
return new BooleanStaticAssertionPredicate((CriteriaBuilderImpl) theBuilder, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (myDontUseHashesForSearch) {
|
||||||
|
ArrayList<Predicate> singleCodePredicates = new ArrayList<Predicate>();
|
||||||
|
if (codes != null) {
|
||||||
|
|
||||||
|
List<Predicate> orPredicates = new ArrayList<Predicate>();
|
||||||
|
Map<String, List<VersionIndependentConcept>> map = new HashMap<String, List<VersionIndependentConcept>>();
|
||||||
|
for (VersionIndependentConcept nextCode : codes) {
|
||||||
|
List<VersionIndependentConcept> systemCodes = map.get(nextCode.getSystem());
|
||||||
|
if (null == systemCodes) {
|
||||||
|
systemCodes = new ArrayList<>();
|
||||||
|
map.put(nextCode.getSystem(), systemCodes);
|
||||||
|
}
|
||||||
|
systemCodes.add(nextCode);
|
||||||
|
}
|
||||||
|
// Use "in" in case of large numbers of codes due to param modifiers
|
||||||
|
final Path<String> systemExpression = theFrom.get("mySystem");
|
||||||
|
final Path<String> valueExpression = theFrom.get("myValue");
|
||||||
|
for (Map.Entry<String, List<VersionIndependentConcept>> entry : map.entrySet()) {
|
||||||
|
CriteriaBuilder.In<String> codePredicate = theBuilder.in(valueExpression);
|
||||||
|
boolean haveAtLeastOneCode = false;
|
||||||
|
for (VersionIndependentConcept nextCode : entry.getValue()) {
|
||||||
|
if (isNotBlank(nextCode.getCode())) {
|
||||||
|
codePredicate.value(nextCode.getCode());
|
||||||
|
haveAtLeastOneCode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.getKey() != null) {
|
||||||
|
Predicate systemPredicate = theBuilder.equal(systemExpression, entry.getKey());
|
||||||
|
if (haveAtLeastOneCode) {
|
||||||
|
orPredicates.add(theBuilder.and(systemPredicate, codePredicate));
|
||||||
|
} else {
|
||||||
|
orPredicates.add(systemPredicate);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
orPredicates.add(codePredicate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Predicate or = theBuilder.or(orPredicates.toArray(new Predicate[0]));
|
||||||
|
if (modifier == TokenParamModifier.NOT) {
|
||||||
|
or = theBuilder.not(or);
|
||||||
|
}
|
||||||
|
singleCodePredicates.add(or);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ok, this is a normal query
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(system)) {
|
||||||
|
if (modifier != null && modifier == TokenParamModifier.NOT) {
|
||||||
|
singleCodePredicates.add(theBuilder.notEqual(theFrom.get("mySystem"), system));
|
||||||
|
} else {
|
||||||
|
singleCodePredicates.add(theBuilder.equal(theFrom.get("mySystem"), system));
|
||||||
|
}
|
||||||
|
} else if (system == null) {
|
||||||
|
// don't check the system
|
||||||
|
} else {
|
||||||
|
// If the system is "", we only match on null systems
|
||||||
|
singleCodePredicates.add(theBuilder.isNull(theFrom.get("mySystem")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(code)) {
|
||||||
|
if (modifier != null && modifier == TokenParamModifier.NOT) {
|
||||||
|
singleCodePredicates.add(theBuilder.notEqual(theFrom.get("myValue"), code));
|
||||||
|
} else {
|
||||||
|
singleCodePredicates.add(theBuilder.equal(theFrom.get("myValue"), code));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* As of HAPI FHIR 1.5, if the client searched for a token with a system but no specified value this means to
|
||||||
|
* match all tokens with the given value.
|
||||||
|
*
|
||||||
|
* I'm not sure I agree with this, but hey.. FHIR-I voted and this was the result :)
|
||||||
|
*/
|
||||||
|
// singleCodePredicates.add(theBuilder.isNull(theFrom.get("myValue")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Predicate singleCode = theBuilder.and(toArray(singleCodePredicates));
|
||||||
|
return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Note: A null system value means "match any system", but
|
* Note: A null system value means "match any system", but
|
||||||
* an empty-string system value means "match values that
|
* an empty-string system value means "match values that
|
||||||
|
@ -1607,9 +1776,14 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
if (param.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
|
if (param.getParamType() == RestSearchParameterTypeEnum.REFERENCE) {
|
||||||
thePredicates.add(join.get("mySourcePath").as(String.class).in(param.getPathsSplit()));
|
thePredicates.add(join.get("mySourcePath").as(String.class).in(param.getPathsSplit()));
|
||||||
} else {
|
} else {
|
||||||
Long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(myResourceName, theSort.getParamName());
|
if (myDontUseHashesForSearch) {
|
||||||
Predicate joinParam1 = theBuilder.equal(join.get("myHashIdentity"), hashIdentity);
|
Predicate joinParam1 = theBuilder.equal(join.get("myParamName"), theSort.getParamName());
|
||||||
thePredicates.add(joinParam1);
|
thePredicates.add(joinParam1);
|
||||||
|
} else {
|
||||||
|
Long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(myResourceName, theSort.getParamName());
|
||||||
|
Predicate joinParam1 = theBuilder.equal(join.get("myHashIdentity"), hashIdentity);
|
||||||
|
thePredicates.add(joinParam1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ourLog.debug("Reusing join for {}", theSort.getParamName());
|
ourLog.debug("Reusing join for {}", theSort.getParamName());
|
||||||
|
@ -1668,7 +1842,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
//-- preload all tags with tag definition if any
|
//-- preload all tags with tag definition if any
|
||||||
Map<Long, Collection<ResourceTag>> tagMap = getResourceTagMap(resourceSearchViewList);
|
Map<Long, Collection<ResourceTag>> tagMap = getResourceTagMap(resourceSearchViewList);
|
||||||
|
|
||||||
Long resourceId = null;
|
Long resourceId;
|
||||||
for (ResourceSearchView next : resourceSearchViewList) {
|
for (ResourceSearchView next : resourceSearchViewList) {
|
||||||
|
|
||||||
Class<? extends IBaseResource> resourceType = context.getResourceDefinition(next.getResourceType()).getImplementingClass();
|
Class<? extends IBaseResource> resourceType = context.getResourceDefinition(next.getResourceType()).getImplementingClass();
|
||||||
|
@ -1706,7 +1880,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
|
|
||||||
private Map<Long, Collection<ResourceTag>> getResourceTagMap(Collection<ResourceSearchView> theResourceSearchViewList) {
|
private Map<Long, Collection<ResourceTag>> getResourceTagMap(Collection<ResourceSearchView> theResourceSearchViewList) {
|
||||||
|
|
||||||
List<Long> idList = new ArrayList<Long>(theResourceSearchViewList.size());
|
List<Long> idList = new ArrayList<>(theResourceSearchViewList.size());
|
||||||
|
|
||||||
//-- find all resource has tags
|
//-- find all resource has tags
|
||||||
for (ResourceSearchView resource : theResourceSearchViewList) {
|
for (ResourceSearchView resource : theResourceSearchViewList) {
|
||||||
|
|
|
@ -129,6 +129,8 @@ public abstract class BaseResourceIndexedSearchParam implements Serializable {
|
||||||
|
|
||||||
public abstract IQueryParameterType toQueryParameterType();
|
public abstract IQueryParameterType toQueryParameterType();
|
||||||
|
|
||||||
|
public abstract void calculateHashes();
|
||||||
|
|
||||||
public static long calculateHashIdentity(String theResourceType, String theParamName) {
|
public static long calculateHashIdentity(String theResourceType, String theParamName) {
|
||||||
return hash(theResourceType, theParamName);
|
return hash(theResourceType, theParamName);
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ public class ResourceIndexedSearchParamCoords extends BaseResourceIndexedSearchP
|
||||||
setLongitude(theLongitude);
|
setLongitude(theLongitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@PrePersist
|
@PrePersist
|
||||||
public void calculateHashes() {
|
public void calculateHashes() {
|
||||||
if (myHashIdentity == null) {
|
if (myHashIdentity == null) {
|
||||||
|
|
|
@ -83,6 +83,7 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
|
||||||
myOriginalValue = theOriginalValue;
|
myOriginalValue = theOriginalValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@PrePersist
|
@PrePersist
|
||||||
public void calculateHashes() {
|
public void calculateHashes() {
|
||||||
if (myHashIdentity == null) {
|
if (myHashIdentity == null) {
|
||||||
|
|
|
@ -69,6 +69,7 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
|
||||||
setValue(theValue);
|
setValue(theValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@PrePersist
|
@PrePersist
|
||||||
public void calculateHashes() {
|
public void calculateHashes() {
|
||||||
if (myHashIdentity == null) {
|
if (myHashIdentity == null) {
|
||||||
|
|
|
@ -95,6 +95,7 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc
|
||||||
setUnits(theUnits);
|
setUnits(theUnits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@PrePersist
|
@PrePersist
|
||||||
public void calculateHashes() {
|
public void calculateHashes() {
|
||||||
if (myHashIdentity == null) {
|
if (myHashIdentity == null) {
|
||||||
|
|
|
@ -161,6 +161,7 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
|
||||||
myHashIdentity = theHashIdentity;
|
myHashIdentity = theHashIdentity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@PrePersist
|
@PrePersist
|
||||||
public void calculateHashes() {
|
public void calculateHashes() {
|
||||||
if (myHashNormalizedPrefix == null && myDaoConfig != null) {
|
if (myHashNormalizedPrefix == null && myDaoConfig != null) {
|
||||||
|
|
|
@ -108,6 +108,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
|
||||||
setValue(theValue);
|
setValue(theValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@PrePersist
|
@PrePersist
|
||||||
public void calculateHashes() {
|
public void calculateHashes() {
|
||||||
if (myHashSystem == null) {
|
if (myHashSystem == null) {
|
||||||
|
|
|
@ -82,6 +82,7 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
|
||||||
setUri(theUri);
|
setUri(theUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@PrePersist
|
@PrePersist
|
||||||
public void calculateHashes() {
|
public void calculateHashes() {
|
||||||
if (myHashUri == null) {
|
if (myHashUri == null) {
|
||||||
|
|
|
@ -39,6 +39,7 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.param.StringParam;
|
import ca.uhn.fhir.rest.param.StringParam;
|
||||||
import ca.uhn.fhir.rest.param.UriParam;
|
import ca.uhn.fhir.rest.param.UriParam;
|
||||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||||
import ca.uhn.fhir.util.ParametersUtil;
|
import ca.uhn.fhir.util.ParametersUtil;
|
||||||
|
@ -307,7 +308,7 @@ public class SubscriptionTriggeringProvider implements IResourceProvider, Applic
|
||||||
ourLog.info("Triggering job[{}] search {} requesting resources {} - {}", theJobDetails.getJobId(), theJobDetails.getCurrentSearchUuid(), fromIndex, toIndex);
|
ourLog.info("Triggering job[{}] search {} requesting resources {} - {}", theJobDetails.getJobId(), theJobDetails.getCurrentSearchUuid(), fromIndex, toIndex);
|
||||||
List<Long> resourceIds = mySearchCoordinatorSvc.getResources(theJobDetails.getCurrentSearchUuid(), fromIndex, toIndex);
|
List<Long> resourceIds = mySearchCoordinatorSvc.getResources(theJobDetails.getCurrentSearchUuid(), fromIndex, toIndex);
|
||||||
|
|
||||||
ourLog.info("Triggering job[{}] delivering {} resources", theJobDetails.getJobId(), theJobDetails.getCurrentSearchUuid(), fromIndex, toIndex);
|
ourLog.info("Triggering job[{}] delivering {} resources", theJobDetails.getJobId(), resourceIds.size());
|
||||||
int highestIndexSubmitted = theJobDetails.getCurrentSearchLastUploadedIndex();
|
int highestIndexSubmitted = theJobDetails.getCurrentSearchLastUploadedIndex();
|
||||||
|
|
||||||
for (Long next : resourceIds) {
|
for (Long next : resourceIds) {
|
||||||
|
@ -374,9 +375,22 @@ public class SubscriptionTriggeringProvider implements IResourceProvider, Applic
|
||||||
msg.setNewPayload(myFhirContext, theResourceToTrigger);
|
msg.setNewPayload(myFhirContext, theResourceToTrigger);
|
||||||
|
|
||||||
return myExecutorService.submit(()->{
|
return myExecutorService.submit(()->{
|
||||||
for (BaseSubscriptionInterceptor<?> next : mySubscriptionInterceptorList) {
|
for (int i = 0; ; i++) {
|
||||||
next.submitResourceModified(msg);
|
try {
|
||||||
|
for (BaseSubscriptionInterceptor<?> next : mySubscriptionInterceptorList) {
|
||||||
|
next.submitResourceModified(msg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (i >= 3) {
|
||||||
|
throw new InternalErrorException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
ourLog.warn("Exception while retriggering subscriptions (going to sleep and retry): {}", e.toString());
|
||||||
|
Thread.sleep(1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,6 @@ public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc {
|
||||||
|
|
||||||
private void deleteSearch(final Long theSearchPid) {
|
private void deleteSearch(final Long theSearchPid) {
|
||||||
mySearchDao.findById(theSearchPid).ifPresent(searchToDelete -> {
|
mySearchDao.findById(theSearchPid).ifPresent(searchToDelete -> {
|
||||||
ourLog.info("Deleting search {}/{} - Created[{}] -- Last returned[{}]", searchToDelete.getId(), searchToDelete.getUuid(), new InstantType(searchToDelete.getCreated()), new InstantType(searchToDelete.getSearchLastReturned()));
|
|
||||||
mySearchIncludeDao.deleteForSearch(searchToDelete.getId());
|
mySearchIncludeDao.deleteForSearch(searchToDelete.getId());
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -93,7 +92,10 @@ public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc {
|
||||||
|
|
||||||
// Only delete if we don't have results left in this search
|
// Only delete if we don't have results left in this search
|
||||||
if (resultPids.getNumberOfElements() < max) {
|
if (resultPids.getNumberOfElements() < max) {
|
||||||
|
ourLog.info("Deleting search {}/{} - Created[{}] -- Last returned[{}]", searchToDelete.getId(), searchToDelete.getUuid(), new InstantType(searchToDelete.getCreated()), new InstantType(searchToDelete.getSearchLastReturned()));
|
||||||
mySearchDao.deleteByPid(searchToDelete.getId());
|
mySearchDao.deleteByPid(searchToDelete.getId());
|
||||||
|
} else {
|
||||||
|
ourLog.info("Purged {} search results for deleted search {}/{}", resultPids.getSize(), searchToDelete.getId(), searchToDelete.getUuid());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -478,8 +478,6 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
||||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
|
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void afterCommit() {
|
public void afterCommit() {
|
||||||
// FIXME: remove
|
|
||||||
ourLog.info("** Sending processing message " + theMessage + " for: " + theMessage.getNewPayload(myCtx));
|
|
||||||
ourLog.trace("Sending resource modified message to processing channel");
|
ourLog.trace("Sending resource modified message to processing channel");
|
||||||
getProcessingChannel().send(new ResourceModifiedJsonMessage(theMessage));
|
getProcessingChannel().send(new ResourceModifiedJsonMessage(theMessage));
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,10 +108,6 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe
|
||||||
operation.encoded(thePayloadType);
|
operation.encoded(thePayloadType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: remove
|
|
||||||
ourLog.info("** This " + this + " Processing delivery message " + theMsg);
|
|
||||||
|
|
||||||
|
|
||||||
ourLog.info("Delivering {} rest-hook payload {} for {}", theMsg.getOperationType(), thePayloadResource.getIdElement().toUnqualified().getValue(), theSubscription.getIdElement(getContext()).toUnqualifiedVersionless().getValue());
|
ourLog.info("Delivering {} rest-hook payload {} for {}", theMsg.getOperationType(), thePayloadResource.getIdElement().toUnqualified().getValue(), theSubscription.getIdElement(getContext()).toUnqualifiedVersionless().getValue());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.config;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
|
||||||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||||
|
import net.ttddyy.dsproxy.listener.SingleQueryCountHolder;
|
||||||
import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder;
|
import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder;
|
||||||
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
|
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
|
||||||
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
|
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
|
||||||
|
@ -107,12 +108,18 @@ public class TestR4Config extends BaseJavaConfigR4 {
|
||||||
.create(retVal)
|
.create(retVal)
|
||||||
.logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL")
|
.logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL")
|
||||||
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
|
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
|
||||||
.countQuery(new ThreadQueryCountHolder())
|
// .countQuery(new ThreadQueryCountHolder())
|
||||||
|
.countQuery(singleQueryCountHolder())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return dataSource;
|
return dataSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SingleQueryCountHolder singleQueryCountHolder() {
|
||||||
|
return new SingleQueryCountHolder();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Bean()
|
@Bean()
|
||||||
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
|
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
|
||||||
|
|
|
@ -289,7 +289,7 @@ public abstract class BaseJpaTest {
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<IIdType> toUnqualifiedVersionlessIds(List<IBaseResource> theFound) {
|
protected List<IIdType> toUnqualifiedVersionlessIds(List<? extends IBaseResource> theFound) {
|
||||||
List<IIdType> retVal = new ArrayList<IIdType>();
|
List<IIdType> retVal = new ArrayList<IIdType>();
|
||||||
for (IBaseResource next : theFound) {
|
for (IBaseResource next : theFound) {
|
||||||
retVal.add(next.getIdElement().toUnqualifiedVersionless());
|
retVal.add(next.getIdElement().toUnqualifiedVersionless());
|
||||||
|
|
|
@ -1613,16 +1613,18 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
|
||||||
obs01.setSubject(new Reference(patientId01));
|
obs01.setSubject(new Reference(patientId01));
|
||||||
IIdType obsId01 = myObservationDao.create(obs01, mySrd).getId().toUnqualifiedVersionless();
|
IIdType obsId01 = myObservationDao.create(obs01, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
|
||||||
Date between = new Date();
|
Date between = new Date();
|
||||||
Thread.sleep(10);
|
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
|
||||||
|
|
||||||
Observation obs02 = new Observation();
|
Observation obs02 = new Observation();
|
||||||
obs02.setEffective(new DateTimeType(new Date()));
|
obs02.setEffective(new DateTimeType(new Date()));
|
||||||
obs02.setSubject(new Reference(locId01));
|
obs02.setSubject(new Reference(locId01));
|
||||||
IIdType obsId02 = myObservationDao.create(obs02, mySrd).getId().toUnqualifiedVersionless();
|
IIdType obsId02 = myObservationDao.create(obs02, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
Thread.sleep(10);
|
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
|
||||||
Date after = new Date();
|
Date after = new Date();
|
||||||
|
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
|
||||||
|
|
||||||
ourLog.info("P1[{}] L1[{}] Obs1[{}] Obs2[{}]", new Object[] { patientId01, locId01, obsId01, obsId02 });
|
ourLog.info("P1[{}] L1[{}] Obs1[{}] Obs2[{}]", new Object[] { patientId01, locId01, obsId01, obsId02 });
|
||||||
|
|
||||||
|
|
|
@ -2865,16 +2865,22 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
|
||||||
p.addName().setFamily(methodName);
|
p.addName().setFamily(methodName);
|
||||||
IIdType id1 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
IIdType id1 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
|
||||||
|
|
||||||
p = new Patient();
|
p = new Patient();
|
||||||
p.addIdentifier().setSystem("urn:system2").setValue(methodName);
|
p.addIdentifier().setSystem("urn:system2").setValue(methodName);
|
||||||
p.addName().setFamily(methodName);
|
p.addName().setFamily(methodName);
|
||||||
IIdType id2 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
IIdType id2 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
|
||||||
|
|
||||||
p = new Patient();
|
p = new Patient();
|
||||||
p.addIdentifier().setSystem("urn:system3").setValue(methodName);
|
p.addIdentifier().setSystem("urn:system3").setValue(methodName);
|
||||||
p.addName().setFamily(methodName);
|
p.addName().setFamily(methodName);
|
||||||
IIdType id3 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
IIdType id3 = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast(1);
|
||||||
|
|
||||||
p = new Patient();
|
p = new Patient();
|
||||||
p.addIdentifier().setSystem("urn:system4").setValue(methodName);
|
p.addIdentifier().setSystem("urn:system4").setValue(methodName);
|
||||||
p.addName().setFamily(methodName);
|
p.addName().setFamily(methodName);
|
||||||
|
|
|
@ -4,7 +4,6 @@ import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||||
import ca.uhn.fhir.rest.param.StringParam;
|
import ca.uhn.fhir.rest.param.StringParam;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder;
|
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.Bundle;
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
import org.hl7.fhir.r4.model.IdType;
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
|
@ -18,10 +17,9 @@ import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.contains;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.hamcrest.Matchers.empty;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.hamcrest.Matchers.matchesPattern;
|
import static org.junit.Assert.assertThat;
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
|
public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4CreateTest.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4CreateTest.class);
|
||||||
|
@ -37,22 +35,22 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
|
||||||
Patient p = myFhirCtx.newXmlParser().parseResource(Patient.class, input);
|
Patient p = myFhirCtx.newXmlParser().parseResource(Patient.class, input);
|
||||||
String id = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue();
|
String id = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue();
|
||||||
|
|
||||||
SearchParameterMap map= new SearchParameterMap();
|
SearchParameterMap map = new SearchParameterMap();
|
||||||
map.setLoadSynchronous(true);
|
map.setLoadSynchronous(true);
|
||||||
map.add(Patient.SP_FAMILY, new StringParam("김"));
|
map.add(Patient.SP_FAMILY, new StringParam("김"));
|
||||||
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(id));
|
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(id));
|
||||||
|
|
||||||
map= new SearchParameterMap();
|
map = new SearchParameterMap();
|
||||||
map.setLoadSynchronous(true);
|
map.setLoadSynchronous(true);
|
||||||
map.add(Patient.SP_GIVEN, new StringParam("준"));
|
map.add(Patient.SP_GIVEN, new StringParam("준"));
|
||||||
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(id));
|
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(id));
|
||||||
|
|
||||||
map= new SearchParameterMap();
|
map = new SearchParameterMap();
|
||||||
map.setLoadSynchronous(true);
|
map.setLoadSynchronous(true);
|
||||||
map.add(Patient.SP_GIVEN, new StringParam("준수"));
|
map.add(Patient.SP_GIVEN, new StringParam("준수"));
|
||||||
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(id));
|
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(id));
|
||||||
|
|
||||||
map= new SearchParameterMap();
|
map = new SearchParameterMap();
|
||||||
map.setLoadSynchronous(true);
|
map.setLoadSynchronous(true);
|
||||||
map.add(Patient.SP_GIVEN, new StringParam("수")); // rightmost character only
|
map.add(Patient.SP_GIVEN, new StringParam("수")); // rightmost character only
|
||||||
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), empty());
|
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), empty());
|
||||||
|
@ -60,7 +58,7 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateWithUuidResourceStrategy() throws Exception {
|
public void testCreateWithUuidResourceStrategy() {
|
||||||
myDaoConfig.setResourceServerIdStrategy(DaoConfig.IdStrategyEnum.UUID);
|
myDaoConfig.setResourceServerIdStrategy(DaoConfig.IdStrategyEnum.UUID);
|
||||||
|
|
||||||
Patient p = new Patient();
|
Patient p = new Patient();
|
||||||
|
@ -110,26 +108,6 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
|
||||||
assertThat(output.getEntry().get(1).getResponse().getLocation(), matchesPattern("Patient/[a-z0-9]{8}-.*"));
|
assertThat(output.getEntry().get(1).getResponse().getLocation(), matchesPattern("Patient/[a-z0-9]{8}-.*"));
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWritesPerformMinimalSqlStatements() {
|
|
||||||
Patient p = new Patient();
|
|
||||||
p.addIdentifier().setSystem("sys1").setValue("val1");
|
|
||||||
p.addIdentifier().setSystem("sys2").setValue("val2");
|
|
||||||
|
|
||||||
ourLog.info("** About to perform write");
|
|
||||||
new ThreadQueryCountHolder().getOrCreateQueryCount("").setInsert(0);
|
|
||||||
new ThreadQueryCountHolder().getOrCreateQueryCount("").setUpdate(0);
|
|
||||||
|
|
||||||
myPatientDao.create(p);
|
|
||||||
|
|
||||||
ourLog.info("** Done performing write");
|
|
||||||
|
|
||||||
ourLog.info("Inserts: {}", new ThreadQueryCountHolder().getOrCreateQueryCount("").getInsert());
|
|
||||||
ourLog.info("Updates: {}", new ThreadQueryCountHolder().getOrCreateQueryCount("").getUpdate());
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
package ca.uhn.fhir.jpa.dao.r4;
|
package ca.uhn.fhir.jpa.dao.r4;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
|
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||||
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
|
import net.ttddyy.dsproxy.QueryCount;
|
||||||
import net.ttddyy.dsproxy.QueryCountHolder;
|
import net.ttddyy.dsproxy.QueryCountHolder;
|
||||||
|
import net.ttddyy.dsproxy.listener.SingleQueryCountHolder;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.DateTimeType;
|
import org.hl7.fhir.r4.model.DateTimeType;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.test.context.TestPropertySource;
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
@ -18,6 +23,8 @@ import static org.junit.Assert.assertEquals;
|
||||||
})
|
})
|
||||||
public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4QueryCountTest.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4QueryCountTest.class);
|
||||||
|
@Autowired
|
||||||
|
private SingleQueryCountHolder myCountHolder;
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void afterResetDao() {
|
public void afterResetDao() {
|
||||||
|
@ -25,22 +32,87 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
||||||
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
|
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWritesPerformMinimalSqlStatements() {
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.addIdentifier().setSystem("sys1").setValue("val1");
|
||||||
|
p.addIdentifier().setSystem("sys2").setValue("val2");
|
||||||
|
|
||||||
|
ourLog.info("** About to perform write");
|
||||||
|
myCountHolder.clear();
|
||||||
|
|
||||||
|
IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
ourLog.info("** Done performing write");
|
||||||
|
|
||||||
|
assertEquals(6, getQueryCount().getInsert());
|
||||||
|
assertEquals(0, getQueryCount().getUpdate());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Not update the value
|
||||||
|
*/
|
||||||
|
|
||||||
|
p = new Patient();
|
||||||
|
p.setId(id);
|
||||||
|
p.addIdentifier().setSystem("sys1").setValue("val3");
|
||||||
|
p.addIdentifier().setSystem("sys2").setValue("val4");
|
||||||
|
|
||||||
|
ourLog.info("** About to perform write 2");
|
||||||
|
myCountHolder.clear();
|
||||||
|
|
||||||
|
myPatientDao.update(p).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
ourLog.info("** Done performing write 2");
|
||||||
|
|
||||||
|
assertEquals(2, getQueryCount().getInsert());
|
||||||
|
assertEquals(1, getQueryCount().getUpdate());
|
||||||
|
assertEquals(1, getQueryCount().getDelete());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearch() {
|
||||||
|
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
Patient p = new Patient();
|
||||||
|
p.addIdentifier().setSystem("sys1").setValue("val" + i);
|
||||||
|
myPatientDao.create(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
myCountHolder.clear();
|
||||||
|
|
||||||
|
ourLog.info("** About to perform search");
|
||||||
|
IBundleProvider search = myPatientDao.search(new SearchParameterMap());
|
||||||
|
ourLog.info("** About to retrieve resources");
|
||||||
|
search.getResources(0, 20);
|
||||||
|
ourLog.info("** Done retrieving resources");
|
||||||
|
|
||||||
|
assertEquals(4, getQueryCount().getSelect());
|
||||||
|
assertEquals(2, getQueryCount().getInsert());
|
||||||
|
assertEquals(1, getQueryCount().getUpdate());
|
||||||
|
assertEquals(0, getQueryCount().getDelete());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private QueryCount getQueryCount() {
|
||||||
|
return myCountHolder.getQueryCountMap().get("");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateClientAssignedId() {
|
public void testCreateClientAssignedId() {
|
||||||
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
|
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
|
||||||
|
|
||||||
QueryCountHolder.clear();
|
myCountHolder.clear();
|
||||||
ourLog.info("** Starting Update Non-Existing resource with client assigned ID");
|
ourLog.info("** Starting Update Non-Existing resource with client assigned ID");
|
||||||
Patient p = new Patient();
|
Patient p = new Patient();
|
||||||
p.setId("A");
|
p.setId("A");
|
||||||
p.getPhotoFirstRep().setCreationElement(new DateTimeType("2011")); // non-indexed field
|
p.getPhotoFirstRep().setCreationElement(new DateTimeType("2011")); // non-indexed field
|
||||||
myPatientDao.update(p).getId().toUnqualifiedVersionless();
|
myPatientDao.update(p).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
assertEquals(1, QueryCountHolder.getGrandTotal().getSelect());
|
assertEquals(1, getQueryCount().getSelect());
|
||||||
assertEquals(4, QueryCountHolder.getGrandTotal().getInsert());
|
assertEquals(4, getQueryCount().getInsert());
|
||||||
assertEquals(0, QueryCountHolder.getGrandTotal().getDelete());
|
assertEquals(0, getQueryCount().getDelete());
|
||||||
// Because of the forced ID's bidirectional link HFJ_RESOURCE <-> HFJ_FORCED_ID
|
// Because of the forced ID's bidirectional link HFJ_RESOURCE <-> HFJ_FORCED_ID
|
||||||
assertEquals(1, QueryCountHolder.getGrandTotal().getUpdate());
|
assertEquals(1, getQueryCount().getUpdate());
|
||||||
runInTransaction(() -> {
|
runInTransaction(() -> {
|
||||||
assertEquals(1, myResourceTableDao.count());
|
assertEquals(1, myResourceTableDao.count());
|
||||||
assertEquals(1, myResourceHistoryTableDao.count());
|
assertEquals(1, myResourceHistoryTableDao.count());
|
||||||
|
@ -50,17 +122,17 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
||||||
|
|
||||||
// Ok how about an update
|
// Ok how about an update
|
||||||
|
|
||||||
QueryCountHolder.clear();
|
myCountHolder.clear();
|
||||||
ourLog.info("** Starting Update Existing resource with client assigned ID");
|
ourLog.info("** Starting Update Existing resource with client assigned ID");
|
||||||
p = new Patient();
|
p = new Patient();
|
||||||
p.setId("A");
|
p.setId("A");
|
||||||
p.getPhotoFirstRep().setCreationElement(new DateTimeType("2012")); // non-indexed field
|
p.getPhotoFirstRep().setCreationElement(new DateTimeType("2012")); // non-indexed field
|
||||||
myPatientDao.update(p).getId().toUnqualifiedVersionless();
|
myPatientDao.update(p).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
assertEquals(5, QueryCountHolder.getGrandTotal().getSelect());
|
assertEquals(5, getQueryCount().getSelect());
|
||||||
assertEquals(1, QueryCountHolder.getGrandTotal().getInsert());
|
assertEquals(1, getQueryCount().getInsert());
|
||||||
assertEquals(0, QueryCountHolder.getGrandTotal().getDelete());
|
assertEquals(0, getQueryCount().getDelete());
|
||||||
assertEquals(1, QueryCountHolder.getGrandTotal().getUpdate());
|
assertEquals(1, getQueryCount().getUpdate());
|
||||||
runInTransaction(() -> {
|
runInTransaction(() -> {
|
||||||
assertEquals(1, myResourceTableDao.count());
|
assertEquals(1, myResourceTableDao.count());
|
||||||
assertEquals(2, myResourceHistoryTableDao.count());
|
assertEquals(2, myResourceHistoryTableDao.count());
|
||||||
|
@ -75,24 +147,24 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
||||||
public void testOneRowPerUpdate() {
|
public void testOneRowPerUpdate() {
|
||||||
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
|
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
|
||||||
|
|
||||||
QueryCountHolder.clear();
|
myCountHolder.clear();
|
||||||
Patient p = new Patient();
|
Patient p = new Patient();
|
||||||
p.getPhotoFirstRep().setCreationElement(new DateTimeType("2011")); // non-indexed field
|
p.getPhotoFirstRep().setCreationElement(new DateTimeType("2011")); // non-indexed field
|
||||||
IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless();
|
IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
assertEquals(3, QueryCountHolder.getGrandTotal().getInsert());
|
assertEquals(3, getQueryCount().getInsert());
|
||||||
runInTransaction(() -> {
|
runInTransaction(() -> {
|
||||||
assertEquals(1, myResourceTableDao.count());
|
assertEquals(1, myResourceTableDao.count());
|
||||||
assertEquals(1, myResourceHistoryTableDao.count());
|
assertEquals(1, myResourceHistoryTableDao.count());
|
||||||
});
|
});
|
||||||
|
|
||||||
QueryCountHolder.clear();
|
myCountHolder.clear();
|
||||||
p = new Patient();
|
p = new Patient();
|
||||||
p.setId(id);
|
p.setId(id);
|
||||||
p.getPhotoFirstRep().setCreationElement(new DateTimeType("2012")); // non-indexed field
|
p.getPhotoFirstRep().setCreationElement(new DateTimeType("2012")); // non-indexed field
|
||||||
myPatientDao.update(p).getId().toUnqualifiedVersionless();
|
myPatientDao.update(p).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
assertEquals(1, QueryCountHolder.getGrandTotal().getInsert());
|
assertEquals(1, getQueryCount().getInsert());
|
||||||
runInTransaction(() -> {
|
runInTransaction(() -> {
|
||||||
assertEquals(1, myResourceTableDao.count());
|
assertEquals(1, myResourceTableDao.count());
|
||||||
assertEquals(2, myResourceHistoryTableDao.count());
|
assertEquals(2, myResourceHistoryTableDao.count());
|
||||||
|
@ -101,6 +173,34 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateReusesIndexes() {
|
||||||
|
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
|
||||||
|
|
||||||
|
myCountHolder.clear();
|
||||||
|
|
||||||
|
Patient pt = new Patient();
|
||||||
|
pt.setActive(true);
|
||||||
|
pt.addName().setFamily("FAMILY1").addGiven("GIVEN1A").addGiven("GIVEN1B");
|
||||||
|
IIdType id = myPatientDao.create(pt).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
ourLog.info("Now have {} deleted", getQueryCount().getDelete());
|
||||||
|
ourLog.info("Now have {} inserts", getQueryCount().getInsert());
|
||||||
|
myCountHolder.clear();
|
||||||
|
|
||||||
|
ourLog.info("** About to update");
|
||||||
|
|
||||||
|
pt.setId(id);
|
||||||
|
pt.getNameFirstRep().addGiven("GIVEN1C");
|
||||||
|
myPatientDao.update(pt);
|
||||||
|
|
||||||
|
ourLog.info("Now have {} deleted", getQueryCount().getDelete());
|
||||||
|
ourLog.info("Now have {} inserts", getQueryCount().getInsert());
|
||||||
|
assertEquals(0, getQueryCount().getDelete());
|
||||||
|
assertEquals(2, getQueryCount().getInsert());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
public static void afterClassClearContext() {
|
public static void afterClassClearContext() {
|
||||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -668,33 +668,6 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testUpdateReusesIndexes() {
|
|
||||||
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
|
|
||||||
|
|
||||||
QueryCountHolder.clear();
|
|
||||||
|
|
||||||
Patient pt = new Patient();
|
|
||||||
pt.setActive(true);
|
|
||||||
pt.addName().setFamily("FAMILY1").addGiven("GIVEN1A").addGiven("GIVEN1B");
|
|
||||||
IIdType id = myPatientDao.create(pt).getId().toUnqualifiedVersionless();
|
|
||||||
|
|
||||||
ourLog.info("Now have {} deleted", QueryCountHolder.getGrandTotal().getDelete());
|
|
||||||
ourLog.info("Now have {} inserts", QueryCountHolder.getGrandTotal().getInsert());
|
|
||||||
QueryCountHolder.clear();
|
|
||||||
|
|
||||||
ourLog.info("** About to update");
|
|
||||||
|
|
||||||
pt.setId(id);
|
|
||||||
pt.getNameFirstRep().addGiven("GIVEN1C");
|
|
||||||
myPatientDao.update(pt);
|
|
||||||
|
|
||||||
ourLog.info("Now have {} deleted", QueryCountHolder.getGrandTotal().getDelete());
|
|
||||||
ourLog.info("Now have {} inserts", QueryCountHolder.getGrandTotal().getInsert());
|
|
||||||
assertEquals(0, QueryCountHolder.getGrandTotal().getDelete());
|
|
||||||
assertEquals(4, QueryCountHolder.getGrandTotal().getInsert());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateUnknownNumericIdFails() {
|
public void testUpdateUnknownNumericIdFails() {
|
||||||
Patient p = new Patient();
|
Patient p = new Patient();
|
||||||
|
|
|
@ -250,6 +250,8 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigR4Test extends Base
|
||||||
waitForSize(0, ourCreatedObservations);
|
waitForSize(0, ourCreatedObservations);
|
||||||
waitForSize(5, ourUpdatedObservations);
|
waitForSize(5, ourUpdatedObservations);
|
||||||
|
|
||||||
|
ourLog.info("Have observations: {}", toUnqualifiedVersionlessIds(ourUpdatedObservations));
|
||||||
|
|
||||||
Assert.assertFalse(subscription1.getId().equals(subscription2.getId()));
|
Assert.assertFalse(subscription1.getId().equals(subscription2.getId()));
|
||||||
Assert.assertFalse(observation1.getId().isEmpty());
|
Assert.assertFalse(observation1.getId().isEmpty());
|
||||||
Assert.assertFalse(observation2.getId().isEmpty());
|
Assert.assertFalse(observation2.getId().isEmpty());
|
||||||
|
|
|
@ -349,7 +349,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||||
/**
|
/**
|
||||||
* Count length of URL string, but treating unescaped sequences (e.g. ' ') as their unescaped equivalent (%20)
|
* Count length of URL string, but treating unescaped sequences (e.g. ' ') as their unescaped equivalent (%20)
|
||||||
*/
|
*/
|
||||||
protected int escapedLength(String theServletPath) {
|
protected static int escapedLength(String theServletPath) {
|
||||||
int delta = 0;
|
int delta = 0;
|
||||||
for (int i = 0; i < theServletPath.length(); i++) {
|
for (int i = 0; i < theServletPath.length(); i++) {
|
||||||
char next = theServletPath.charAt(i);
|
char next = theServletPath.charAt(i);
|
||||||
|
@ -564,6 +564,20 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||||
return Collections.unmodifiableList(myInterceptors);
|
return Collections.unmodifiableList(myInterceptors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets (or clears) the list of interceptors
|
||||||
|
*
|
||||||
|
* @param theInterceptors The list of interceptors (may be null)
|
||||||
|
*/
|
||||||
|
public void setInterceptors(IServerInterceptor... theInterceptors) {
|
||||||
|
Validate.noNullElements(theInterceptors, "theInterceptors must not contain any null elements");
|
||||||
|
|
||||||
|
myInterceptors.clear();
|
||||||
|
if (theInterceptors != null) {
|
||||||
|
myInterceptors.addAll(Arrays.asList(theInterceptors));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets (or clears) the list of interceptors
|
* Sets (or clears) the list of interceptors
|
||||||
*
|
*
|
||||||
|
@ -597,6 +611,20 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||||
return myPlainProviders;
|
return myPlainProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the non-resource specific providers which implement method calls on this server.
|
||||||
|
*
|
||||||
|
* @see #setResourceProviders(Collection)
|
||||||
|
*/
|
||||||
|
public void setPlainProviders(Collection<Object> theProviders) {
|
||||||
|
Validate.noNullElements(theProviders, "theProviders must not contain any null elements");
|
||||||
|
|
||||||
|
myPlainProviders.clear();
|
||||||
|
if (theProviders != null) {
|
||||||
|
myPlainProviders.addAll(theProviders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the non-resource specific providers which implement method calls on this server.
|
* Sets the non-resource specific providers which implement method calls on this server.
|
||||||
*
|
*
|
||||||
|
@ -615,7 +643,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||||
* @param servletPath the servelet path
|
* @param servletPath the servelet path
|
||||||
* @return created resource path
|
* @return created resource path
|
||||||
*/
|
*/
|
||||||
protected String getRequestPath(String requestFullPath, String servletContextPath, String servletPath) {
|
protected static String getRequestPath(String requestFullPath, String servletContextPath, String servletPath) {
|
||||||
return requestFullPath.substring(escapedLength(servletContextPath) + escapedLength(servletPath));
|
return requestFullPath.substring(escapedLength(servletContextPath) + escapedLength(servletPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -630,6 +658,18 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||||
return myResourceProviders;
|
return myResourceProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the resource providers for this server
|
||||||
|
*/
|
||||||
|
public void setResourceProviders(Collection<IResourceProvider> theProviders) {
|
||||||
|
Validate.noNullElements(theProviders, "theProviders must not contain any null elements");
|
||||||
|
|
||||||
|
myResourceProviders.clear();
|
||||||
|
if (theProviders != null) {
|
||||||
|
myResourceProviders.addAll(theProviders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the resource providers for this server
|
* Sets the resource providers for this server
|
||||||
*/
|
*/
|
||||||
|
@ -1521,34 +1561,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets (or clears) the list of interceptors
|
|
||||||
*
|
|
||||||
* @param theInterceptors The list of interceptors (may be null)
|
|
||||||
*/
|
|
||||||
public void setInterceptors(IServerInterceptor... theInterceptors) {
|
|
||||||
Validate.noNullElements(theInterceptors, "theInterceptors must not contain any null elements");
|
|
||||||
|
|
||||||
myInterceptors.clear();
|
|
||||||
if (theInterceptors != null) {
|
|
||||||
myInterceptors.addAll(Arrays.asList(theInterceptors));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the non-resource specific providers which implement method calls on this server.
|
|
||||||
*
|
|
||||||
* @see #setResourceProviders(Collection)
|
|
||||||
*/
|
|
||||||
public void setPlainProviders(Collection<Object> theProviders) {
|
|
||||||
Validate.noNullElements(theProviders, "theProviders must not contain any null elements");
|
|
||||||
|
|
||||||
myPlainProviders.clear();
|
|
||||||
if (theProviders != null) {
|
|
||||||
myPlainProviders.addAll(theProviders);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the non-resource specific providers which implement method calls on this server
|
* Sets the non-resource specific providers which implement method calls on this server
|
||||||
*
|
*
|
||||||
|
@ -1563,18 +1575,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the resource providers for this server
|
|
||||||
*/
|
|
||||||
public void setResourceProviders(Collection<IResourceProvider> theProviders) {
|
|
||||||
Validate.noNullElements(theProviders, "theProviders must not contain any null elements");
|
|
||||||
|
|
||||||
myResourceProviders.clear();
|
|
||||||
if (theProviders != null) {
|
|
||||||
myResourceProviders.addAll(theProviders);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If provided (default is <code>null</code>), the tenant identification
|
* If provided (default is <code>null</code>), the tenant identification
|
||||||
* strategy provides a mechanism for a multitenant server to identify which tenant
|
* strategy provides a mechanism for a multitenant server to identify which tenant
|
||||||
|
@ -1585,7 +1585,8 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void throwUnknownFhirOperationException(RequestDetails requestDetails, String requestPath, RequestTypeEnum theRequestType) {
|
protected void throwUnknownFhirOperationException(RequestDetails requestDetails, String requestPath, RequestTypeEnum theRequestType) {
|
||||||
throw new InvalidRequestException(myFhirContext.getLocalizer().getMessage(RestfulServer.class, "unknownMethod", theRequestType.name(), requestPath, requestDetails.getParameters().keySet()));
|
FhirContext fhirContext = myFhirContext;
|
||||||
|
throwUnknownFhirOperationException(requestDetails, requestPath, theRequestType, fhirContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void throwUnknownResourceTypeException(String theResourceName) {
|
protected void throwUnknownResourceTypeException(String theResourceName) {
|
||||||
|
@ -1647,6 +1648,10 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
|
||||||
theResponse.getWriter().write(theException.getMessage());
|
theResponse.getWriter().write(theException.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void throwUnknownFhirOperationException(RequestDetails requestDetails, String requestPath, RequestTypeEnum theRequestType, FhirContext theFhirContext) {
|
||||||
|
throw new InvalidRequestException(theFhirContext.getLocalizer().getMessage(RestfulServer.class, "unknownMethod", theRequestType.name(), requestPath, requestDetails.getParameters().keySet()));
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean partIsOperation(String nextString) {
|
private static boolean partIsOperation(String nextString) {
|
||||||
return nextString.length() > 0 && (nextString.charAt(0) == '_' || nextString.charAt(0) == '$' || nextString.equals(Constants.URL_TOKEN_METADATA));
|
return nextString.length() > 0 && (nextString.charAt(0) == '_' || nextString.charAt(0) == '$' || nextString.equals(Constants.URL_TOKEN_METADATA));
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ import org.apache.commons.io.IOUtils;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
@ -223,6 +224,7 @@ public abstract class BaseMethodBinding<T> {
|
||||||
*/
|
*/
|
||||||
public abstract String getResourceName();
|
public abstract String getResourceName();
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
public abstract RestOperationTypeEnum getRestOperationType();
|
public abstract RestOperationTypeEnum getRestOperationType();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -37,6 +37,8 @@ import ca.uhn.fhir.rest.server.SimpleBundleProvider;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding {
|
public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
|
|
||||||
public ConformanceMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
|
public ConformanceMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
|
||||||
|
@ -86,6 +88,7 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public RestOperationTypeEnum getRestOperationType() {
|
public RestOperationTypeEnum getRestOperationType() {
|
||||||
return RestOperationTypeEnum.METADATA;
|
return RestOperationTypeEnum.METADATA;
|
||||||
|
|
|
@ -36,6 +36,8 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
public class CreateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam {
|
public class CreateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam {
|
||||||
|
|
||||||
public CreateMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
|
public CreateMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
|
||||||
|
@ -47,6 +49,7 @@ public class CreateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public RestOperationTypeEnum getRestOperationType() {
|
public RestOperationTypeEnum getRestOperationType() {
|
||||||
return RestOperationTypeEnum.CREATE;
|
return RestOperationTypeEnum.CREATE;
|
||||||
|
|
|
@ -30,12 +30,15 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
public class DeleteMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceIdButNoResourceBody {
|
public class DeleteMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceIdButNoResourceBody {
|
||||||
|
|
||||||
public DeleteMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
|
public DeleteMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
|
||||||
super(theMethod, theContext, theProvider, Delete.class, theMethod.getAnnotation(Delete.class).type());
|
super(theMethod, theContext, theProvider, Delete.class, theMethod.getAnnotation(Delete.class).type());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public RestOperationTypeEnum getRestOperationType() {
|
public RestOperationTypeEnum getRestOperationType() {
|
||||||
return RestOperationTypeEnum.DELETE;
|
return RestOperationTypeEnum.DELETE;
|
||||||
|
|
|
@ -30,6 +30,7 @@ import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
@ -49,6 +50,7 @@ public class GraphQLMethodBinding extends BaseMethodBinding<String> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public RestOperationTypeEnum getRestOperationType() {
|
public RestOperationTypeEnum getRestOperationType() {
|
||||||
return RestOperationTypeEnum.GRAPHQL_REQUEST;
|
return RestOperationTypeEnum.GRAPHQL_REQUEST;
|
||||||
|
|
|
@ -39,6 +39,7 @@ import org.apache.commons.lang3.StringUtils;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
@ -91,6 +92,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
return BundleTypeEnum.HISTORY;
|
return BundleTypeEnum.HISTORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public RestOperationTypeEnum getRestOperationType() {
|
public RestOperationTypeEnum getRestOperationType() {
|
||||||
return myResourceOperationType;
|
return myResourceOperationType;
|
||||||
|
|
|
@ -193,7 +193,8 @@ public class MethodUtil {
|
||||||
b.append(" or String or byte[]");
|
b.append(" or String or byte[]");
|
||||||
throw new ConfigurationException(b.toString());
|
throw new ConfigurationException(b.toString());
|
||||||
}
|
}
|
||||||
param = new ResourceParameter((Class<? extends IBaseResource>) parameterType, theProvider, mode);
|
boolean methodIsOperation = theMethod.getAnnotation(Operation.class) != null;
|
||||||
|
param = new ResourceParameter((Class<? extends IBaseResource>) parameterType, theProvider, mode, methodIsOperation);
|
||||||
} else if (nextAnnotation instanceof IdParam) {
|
} else if (nextAnnotation instanceof IdParam) {
|
||||||
param = new NullParameter();
|
param = new NullParameter();
|
||||||
} else if (nextAnnotation instanceof ServerBase) {
|
} else if (nextAnnotation instanceof ServerBase) {
|
||||||
|
|
|
@ -19,50 +19,58 @@ package ca.uhn.fhir.rest.server.method;
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
|
||||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBase;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.annotation.Description;
|
import ca.uhn.fhir.model.api.annotation.Description;
|
||||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||||
import ca.uhn.fhir.rest.annotation.*;
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Operation;
|
||||||
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.*;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
|
import ca.uhn.fhir.rest.api.server.IRestfulServer;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||||
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
|
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBase;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
|
|
||||||
|
public static final String WILDCARD_NAME = "$" + Operation.NAME_MATCH_ALL;
|
||||||
|
private final boolean myIdempotent;
|
||||||
|
private final Integer myIdParamIndex;
|
||||||
|
private final String myName;
|
||||||
|
private final RestOperationTypeEnum myOtherOperatiopnType;
|
||||||
|
private final ReturnTypeEnum myReturnType;
|
||||||
private BundleTypeEnum myBundleType;
|
private BundleTypeEnum myBundleType;
|
||||||
private boolean myCanOperateAtInstanceLevel;
|
private boolean myCanOperateAtInstanceLevel;
|
||||||
private boolean myCanOperateAtServerLevel;
|
private boolean myCanOperateAtServerLevel;
|
||||||
private boolean myCanOperateAtTypeLevel;
|
private boolean myCanOperateAtTypeLevel;
|
||||||
private String myDescription;
|
private String myDescription;
|
||||||
private final boolean myIdempotent;
|
|
||||||
private final Integer myIdParamIndex;
|
|
||||||
private final String myName;
|
|
||||||
private final RestOperationTypeEnum myOtherOperatiopnType;
|
|
||||||
private List<ReturnType> myReturnParams;
|
private List<ReturnType> myReturnParams;
|
||||||
private final ReturnTypeEnum myReturnType;
|
|
||||||
|
|
||||||
protected OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
|
protected OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
|
||||||
boolean theIdempotent, String theOperationName, Class<? extends IBaseResource> theOperationType,
|
boolean theIdempotent, String theOperationName, Class<? extends IBaseResource> theOperationType,
|
||||||
OperationParam[] theReturnParams, BundleTypeEnum theBundleType) {
|
OperationParam[] theReturnParams, BundleTypeEnum theBundleType) {
|
||||||
super(theReturnResourceType, theMethod, theContext, theProvider);
|
super(theReturnResourceType, theMethod, theContext, theProvider);
|
||||||
|
|
||||||
myBundleType = theBundleType;
|
myBundleType = theBundleType;
|
||||||
|
@ -91,7 +99,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
|
|
||||||
if (isBlank(theOperationName)) {
|
if (isBlank(theOperationName)) {
|
||||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' on type " + theMethod.getDeclaringClass().getName() + " is annotated with @" + Operation.class.getSimpleName()
|
throw new ConfigurationException("Method '" + theMethod.getName() + "' on type " + theMethod.getDeclaringClass().getName() + " is annotated with @" + Operation.class.getSimpleName()
|
||||||
+ " but this annotation has no name defined");
|
+ " but this annotation has no name defined");
|
||||||
}
|
}
|
||||||
if (theOperationName.startsWith("$") == false) {
|
if (theOperationName.startsWith("$") == false) {
|
||||||
theOperationName = "$" + theOperationName;
|
theOperationName = "$" + theOperationName;
|
||||||
|
@ -152,15 +160,19 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
}
|
}
|
||||||
|
|
||||||
public OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
|
public OperationMethodBinding(Class<?> theReturnResourceType, Class<? extends IBaseResource> theReturnTypeFromRp, Method theMethod, FhirContext theContext, Object theProvider,
|
||||||
Operation theAnnotation) {
|
Operation theAnnotation) {
|
||||||
this(theReturnResourceType, theReturnTypeFromRp, theMethod, theContext, theProvider, theAnnotation.idempotent(), theAnnotation.name(), theAnnotation.type(), theAnnotation.returnParameters(),
|
this(theReturnResourceType, theReturnTypeFromRp, theMethod, theContext, theProvider, theAnnotation.idempotent(), theAnnotation.name(), theAnnotation.type(), theAnnotation.returnParameters(),
|
||||||
theAnnotation.bundleType());
|
theAnnotation.bundleType());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return myDescription;
|
return myDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDescription(String theDescription) {
|
||||||
|
myDescription = theDescription;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of the operation, starting with "$"
|
* Returns the name of the operation, starting with "$"
|
||||||
*/
|
*/
|
||||||
|
@ -173,6 +185,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
return myBundleType;
|
return myBundleType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public RestOperationTypeEnum getRestOperationType() {
|
public RestOperationTypeEnum getRestOperationType() {
|
||||||
return myOtherOperatiopnType;
|
return myOtherOperatiopnType;
|
||||||
|
@ -189,15 +202,19 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) {
|
||||||
|
if (!myName.equals(theRequest.getOperation())) {
|
||||||
|
if (!myName.equals(WILDCARD_NAME)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (getResourceName() == null) {
|
if (getResourceName() == null) {
|
||||||
if (isNotBlank(theRequest.getResourceName())) {
|
if (isNotBlank(theRequest.getResourceName())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (!getResourceName().equals(theRequest.getResourceName())) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!myName.equals(theRequest.getOperation())) {
|
if (getResourceName() != null && !getResourceName().equals(theRequest.getResourceName())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +238,6 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RestOperationTypeEnum getRestOperationType(RequestDetails theRequestDetails) {
|
public RestOperationTypeEnum getRestOperationType(RequestDetails theRequestDetails) {
|
||||||
RestOperationTypeEnum retVal = super.getRestOperationType(theRequestDetails);
|
RestOperationTypeEnum retVal = super.getRestOperationType(theRequestDetails);
|
||||||
|
@ -304,11 +320,6 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
theDetails.setResource((IBaseResource) theRequestDetails.getUserData().get(OperationParameter.REQUEST_CONTENTS_USERDATA_KEY));
|
theDetails.setResource((IBaseResource) theRequestDetails.getUserData().get(OperationParameter.REQUEST_CONTENTS_USERDATA_KEY));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDescription(String theDescription) {
|
|
||||||
myDescription = theDescription;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class ReturnType {
|
public static class ReturnType {
|
||||||
private int myMax;
|
private int myMax;
|
||||||
private int myMin;
|
private int myMin;
|
||||||
|
@ -322,30 +333,30 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
return myMax;
|
return myMax;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMin() {
|
|
||||||
return myMin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return myName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getType() {
|
|
||||||
return myType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMax(int theMax) {
|
public void setMax(int theMax) {
|
||||||
myMax = theMax;
|
myMax = theMax;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMin() {
|
||||||
|
return myMin;
|
||||||
|
}
|
||||||
|
|
||||||
public void setMin(int theMin) {
|
public void setMin(int theMin) {
|
||||||
myMin = theMin;
|
myMin = theMin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return myName;
|
||||||
|
}
|
||||||
|
|
||||||
public void setName(String theName) {
|
public void setName(String theName) {
|
||||||
myName = theName;
|
myName = theName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return myType;
|
||||||
|
}
|
||||||
|
|
||||||
public void setType(String theType) {
|
public void setType(String theType) {
|
||||||
myType = theType;
|
myType = theType;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -166,6 +167,7 @@ public class PageMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public RestOperationTypeEnum getRestOperationType() {
|
public RestOperationTypeEnum getRestOperationType() {
|
||||||
return RestOperationTypeEnum.GET_PAGE;
|
return RestOperationTypeEnum.GET_PAGE;
|
||||||
|
|
|
@ -38,6 +38,8 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for an operation that has a resource type but not a resource body in the
|
* Base class for an operation that has a resource type but not a resource body in the
|
||||||
* request body
|
* request body
|
||||||
|
@ -86,6 +88,7 @@ public class PatchMethodBinding extends BaseOutcomeReturningMethodBindingWithRes
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public RestOperationTypeEnum getRestOperationType() {
|
public RestOperationTypeEnum getRestOperationType() {
|
||||||
return RestOperationTypeEnum.PATCH;
|
return RestOperationTypeEnum.PATCH;
|
||||||
|
|
|
@ -44,6 +44,8 @@ import ca.uhn.fhir.rest.server.ETagSupportEnum;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.*;
|
import ca.uhn.fhir.rest.server.exceptions.*;
|
||||||
import ca.uhn.fhir.util.DateUtils;
|
import ca.uhn.fhir.util.DateUtils;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
public class ReadMethodBinding extends BaseResourceReturningMethodBinding {
|
public class ReadMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ReadMethodBinding.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ReadMethodBinding.class);
|
||||||
|
|
||||||
|
@ -91,6 +93,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public RestOperationTypeEnum getRestOperationType() {
|
public RestOperationTypeEnum getRestOperationType() {
|
||||||
return isVread() ? RestOperationTypeEnum.VREAD : RestOperationTypeEnum.READ;
|
return isVread() ? RestOperationTypeEnum.VREAD : RestOperationTypeEnum.READ;
|
||||||
|
|
|
@ -38,6 +38,7 @@ import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseBinary;
|
import org.hl7.fhir.instance.model.api.IBaseBinary;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
@ -52,15 +53,17 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
public class ResourceParameter implements IParameter {
|
public class ResourceParameter implements IParameter {
|
||||||
|
|
||||||
|
private final boolean myMethodIsOperation;
|
||||||
private Mode myMode;
|
private Mode myMode;
|
||||||
private Class<? extends IBaseResource> myResourceType;
|
private Class<? extends IBaseResource> myResourceType;
|
||||||
|
|
||||||
public ResourceParameter(Class<? extends IBaseResource> theParameterType, Object theProvider, Mode theMode) {
|
public ResourceParameter(Class<? extends IBaseResource> theParameterType, Object theProvider, Mode theMode, boolean theMethodIsOperation) {
|
||||||
Validate.notNull(theParameterType, "theParameterType can not be null");
|
Validate.notNull(theParameterType, "theParameterType can not be null");
|
||||||
Validate.notNull(theMode, "theMode can not be null");
|
Validate.notNull(theMode, "theMode can not be null");
|
||||||
|
|
||||||
myResourceType = theParameterType;
|
myResourceType = theParameterType;
|
||||||
myMode = theMode;
|
myMode = theMode;
|
||||||
|
myMethodIsOperation = theMethodIsOperation;
|
||||||
|
|
||||||
Class<? extends IBaseResource> providerResourceType = null;
|
Class<? extends IBaseResource> providerResourceType = null;
|
||||||
if (theProvider instanceof IResourceProvider) {
|
if (theProvider instanceof IResourceProvider) {
|
||||||
|
@ -90,24 +93,33 @@ public class ResourceParameter implements IParameter {
|
||||||
@Override
|
@Override
|
||||||
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
|
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
|
||||||
switch (myMode) {
|
switch (myMode) {
|
||||||
case BODY:
|
case BODY:
|
||||||
try {
|
try {
|
||||||
return IOUtils.toString(createRequestReader(theRequest));
|
return IOUtils.toString(createRequestReader(theRequest));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Shouldn't happen since we're reading from a byte array
|
// Shouldn't happen since we're reading from a byte array
|
||||||
throw new InternalErrorException("Failed to load request", e);
|
throw new InternalErrorException("Failed to load request", e);
|
||||||
}
|
}
|
||||||
case BODY_BYTE_ARRAY:
|
case BODY_BYTE_ARRAY:
|
||||||
return theRequest.loadRequestContents();
|
return theRequest.loadRequestContents();
|
||||||
case ENCODING:
|
case ENCODING:
|
||||||
return RestfulServerUtils.determineRequestEncodingNoDefault(theRequest);
|
return RestfulServerUtils.determineRequestEncodingNoDefault(theRequest);
|
||||||
case RESOURCE:
|
case RESOURCE:
|
||||||
default:
|
default:
|
||||||
return parseResourceFromRequest(theRequest, theMethodBinding, myResourceType);
|
Class<? extends IBaseResource> resourceTypeToParse = myResourceType;
|
||||||
|
if (myMethodIsOperation) {
|
||||||
|
// Operations typically have a Parameters resource as the body
|
||||||
|
resourceTypeToParse = null;
|
||||||
|
}
|
||||||
|
return parseResourceFromRequest(theRequest, theMethodBinding, resourceTypeToParse);
|
||||||
}
|
}
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum Mode {
|
||||||
|
BODY, BODY_BYTE_ARRAY, ENCODING, RESOURCE
|
||||||
|
}
|
||||||
|
|
||||||
public static Reader createRequestReader(RequestDetails theRequest, Charset charset) {
|
public static Reader createRequestReader(RequestDetails theRequest, Charset charset) {
|
||||||
Reader requestReader = new InputStreamReader(new ByteArrayInputStream(theRequest.loadRequestContents()), charset);
|
Reader requestReader = new InputStreamReader(new ByteArrayInputStream(theRequest.loadRequestContents()), charset);
|
||||||
return requestReader;
|
return requestReader;
|
||||||
|
@ -118,7 +130,7 @@ public class ResourceParameter implements IParameter {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Charset determineRequestCharset(RequestDetails theRequest) {
|
public static Charset determineRequestCharset(RequestDetails theRequest) {
|
||||||
Charset charset = theRequest.getCharset();
|
Charset charset = theRequest.getCharset();
|
||||||
if (charset == null) {
|
if (charset == null) {
|
||||||
charset = Charset.forName("UTF-8");
|
charset = Charset.forName("UTF-8");
|
||||||
}
|
}
|
||||||
|
@ -126,7 +138,7 @@ public class ResourceParameter implements IParameter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static <T extends IBaseResource> T loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding, Class<T> theResourceType) {
|
public static <T extends IBaseResource> T loadResourceFromRequest(RequestDetails theRequest, @Nonnull BaseMethodBinding<?> theMethodBinding, Class<T> theResourceType) {
|
||||||
FhirContext ctx = theRequest.getServer().getFhirContext();
|
FhirContext ctx = theRequest.getServer().getFhirContext();
|
||||||
|
|
||||||
final Charset charset = determineRequestCharset(theRequest);
|
final Charset charset = determineRequestCharset(theRequest);
|
||||||
|
@ -139,7 +151,6 @@ public class ResourceParameter implements IParameter {
|
||||||
String ctValue = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
|
String ctValue = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
|
||||||
if (ctValue != null) {
|
if (ctValue != null) {
|
||||||
if (ctValue.startsWith("application/x-www-form-urlencoded")) {
|
if (ctValue.startsWith("application/x-www-form-urlencoded")) {
|
||||||
//FIXME potential null access theMethodBinding
|
|
||||||
String msg = theRequest.getServer().getFhirContext().getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getRestOperationType());
|
String msg = theRequest.getServer().getFhirContext().getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getRestOperationType());
|
||||||
throw new InvalidRequestException(msg);
|
throw new InvalidRequestException(msg);
|
||||||
}
|
}
|
||||||
|
@ -155,6 +166,9 @@ public class ResourceParameter implements IParameter {
|
||||||
// This shouldn't happen since we're reading from a byte array..
|
// This shouldn't happen since we're reading from a byte array..
|
||||||
throw new InternalErrorException(e);
|
throw new InternalErrorException(e);
|
||||||
}
|
}
|
||||||
|
if (isBlank(body)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
encoding = EncodingEnum.detectEncodingNoDefault(body);
|
encoding = EncodingEnum.detectEncodingNoDefault(body);
|
||||||
if (encoding == null) {
|
if (encoding == null) {
|
||||||
String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "noContentTypeInRequest", restOperationType);
|
String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "noContentTypeInRequest", restOperationType);
|
||||||
|
@ -168,7 +182,7 @@ public class ResourceParameter implements IParameter {
|
||||||
}
|
}
|
||||||
|
|
||||||
IParser parser = encoding.newParser(ctx);
|
IParser parser = encoding.newParser(ctx);
|
||||||
parser.setServerBaseUrl(theRequest.getFhirServerBase());
|
parser.setServerBaseUrl(theRequest.getFhirServerBase());
|
||||||
T retVal;
|
T retVal;
|
||||||
try {
|
try {
|
||||||
if (theResourceType != null) {
|
if (theResourceType != null) {
|
||||||
|
@ -187,7 +201,7 @@ public class ResourceParameter implements IParameter {
|
||||||
public static IBaseResource parseResourceFromRequest(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding, Class<? extends IBaseResource> theResourceType) {
|
public static IBaseResource parseResourceFromRequest(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding, Class<? extends IBaseResource> theResourceType) {
|
||||||
IBaseResource retVal = null;
|
IBaseResource retVal = null;
|
||||||
|
|
||||||
if (IBaseBinary.class.isAssignableFrom(theResourceType)) {
|
if (theResourceType != null && IBaseBinary.class.isAssignableFrom(theResourceType)) {
|
||||||
String ct = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
|
String ct = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE);
|
||||||
if (EncodingEnum.forContentTypeStrict(ct) == null) {
|
if (EncodingEnum.forContentTypeStrict(ct) == null) {
|
||||||
FhirContext ctx = theRequest.getServer().getFhirContext();
|
FhirContext ctx = theRequest.getServer().getFhirContext();
|
||||||
|
@ -216,8 +230,4 @@ public class ResourceParameter implements IParameter {
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Mode {
|
|
||||||
BODY, BODY_BYTE_ARRAY, ENCODING, RESOURCE
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,8 @@ import ca.uhn.fhir.rest.param.QualifierDetails;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchMethodBinding.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchMethodBinding.class);
|
||||||
|
|
||||||
|
@ -108,6 +110,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
return myDescription;
|
return myDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public RestOperationTypeEnum getRestOperationType() {
|
public RestOperationTypeEnum getRestOperationType() {
|
||||||
return RestOperationTypeEnum.SEARCH_TYPE;
|
return RestOperationTypeEnum.SEARCH_TYPE;
|
||||||
|
|
|
@ -44,6 +44,8 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.method.TransactionParameter.ParamStyle;
|
import ca.uhn.fhir.rest.server.method.TransactionParameter.ParamStyle;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
public class TransactionMethodBinding extends BaseResourceReturningMethodBinding {
|
public class TransactionMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
|
|
||||||
private int myTransactionParamIndex;
|
private int myTransactionParamIndex;
|
||||||
|
@ -73,6 +75,7 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public RestOperationTypeEnum getRestOperationType() {
|
public RestOperationTypeEnum getRestOperationType() {
|
||||||
return RestOperationTypeEnum.TRANSACTION;
|
return RestOperationTypeEnum.TRANSACTION;
|
||||||
|
|
|
@ -39,6 +39,8 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam {
|
public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam {
|
||||||
|
|
||||||
public UpdateMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
|
public UpdateMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
|
||||||
|
@ -98,6 +100,7 @@ public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public RestOperationTypeEnum getRestOperationType() {
|
public RestOperationTypeEnum getRestOperationType() {
|
||||||
return RestOperationTypeEnum.UPDATE;
|
return RestOperationTypeEnum.UPDATE;
|
||||||
|
|
|
@ -0,0 +1,276 @@
|
||||||
|
package ca.uhn.fhir.rest.server;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Operation;
|
||||||
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
|
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import ca.uhn.fhir.util.PortUtil;
|
||||||
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.entity.ContentType;
|
||||||
|
import org.apache.http.entity.StringEntity;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHandler;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
|
import org.hl7.fhir.r4.model.Parameters;
|
||||||
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
|
import org.hl7.fhir.r4.model.StringType;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
|
public class OperationGenericServerR4Test {
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(OperationGenericServerR4Test.class);
|
||||||
|
private static CloseableHttpClient ourClient;
|
||||||
|
private static FhirContext ourCtx;
|
||||||
|
private static IdType ourLastId;
|
||||||
|
private static String ourLastMethod;
|
||||||
|
private static StringType ourLastParam1;
|
||||||
|
private static Patient ourLastParam2;
|
||||||
|
private static int ourPort;
|
||||||
|
private static Server ourServer;
|
||||||
|
private static Parameters ourLastResourceParam;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
ourLastParam1 = null;
|
||||||
|
ourLastParam2 = null;
|
||||||
|
ourLastId = null;
|
||||||
|
ourLastMethod = "";
|
||||||
|
ourLastResourceParam = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOperationOnInstance() throws Exception {
|
||||||
|
Parameters p = new Parameters();
|
||||||
|
p.addParameter().setName("PARAM1").setValue(new StringType("PARAM1val"));
|
||||||
|
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
|
||||||
|
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||||
|
|
||||||
|
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/123/$OP_INSTANCE");
|
||||||
|
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
|
CloseableHttpResponse status = ourClient.execute(httpPost);
|
||||||
|
try {
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
String response = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
status.getEntity().getContent().close();
|
||||||
|
|
||||||
|
assertEquals("PARAM1val", ourLastParam1.getValue());
|
||||||
|
assertEquals(true, ourLastParam2.getActive());
|
||||||
|
assertEquals("123", ourLastId.getIdPart());
|
||||||
|
assertEquals("$OP_INSTANCE", ourLastMethod);
|
||||||
|
assertEquals("PARAM1", ourLastResourceParam.getParameterFirstRep().getName());
|
||||||
|
|
||||||
|
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
|
||||||
|
assertEquals("RET1", resp.getParameter().get(0).getName());
|
||||||
|
} finally {
|
||||||
|
status.getEntity().getContent().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOperationOnServer() throws Exception {
|
||||||
|
Parameters p = new Parameters();
|
||||||
|
p.addParameter().setName("PARAM1").setValue(new StringType("PARAM1val"));
|
||||||
|
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
|
||||||
|
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||||
|
|
||||||
|
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/$OP_SERVER");
|
||||||
|
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
|
CloseableHttpResponse status = ourClient.execute(httpPost);
|
||||||
|
try {
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
String response = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
assertEquals("PARAM1", ourLastResourceParam.getParameterFirstRep().getName());
|
||||||
|
assertEquals("PARAM1val", ourLastParam1.getValue());
|
||||||
|
assertEquals(true, ourLastParam2.getActive());
|
||||||
|
assertEquals("$OP_SERVER", ourLastMethod);
|
||||||
|
|
||||||
|
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
|
||||||
|
assertEquals("RET1", resp.getParameter().get(0).getName());
|
||||||
|
} finally {
|
||||||
|
status.getEntity().getContent().close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOperationOnType() throws Exception {
|
||||||
|
Parameters p = new Parameters();
|
||||||
|
p.addParameter().setName("PARAM1").setValue(new StringType("PARAM1val"));
|
||||||
|
p.addParameter().setName("PARAM2").setResource(new Patient().setActive(true));
|
||||||
|
String inParamsStr = ourCtx.newXmlParser().encodeResourceToString(p);
|
||||||
|
|
||||||
|
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient/$OP_TYPE");
|
||||||
|
httpPost.setEntity(new StringEntity(inParamsStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
|
||||||
|
CloseableHttpResponse status = ourClient.execute(httpPost);
|
||||||
|
try {
|
||||||
|
String response = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
ourLog.info(response);
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
status.getEntity().getContent().close();
|
||||||
|
|
||||||
|
assertEquals("PARAM1", ourLastResourceParam.getParameterFirstRep().getName());
|
||||||
|
assertEquals("PARAM1val", ourLastParam1.getValue());
|
||||||
|
assertEquals(true, ourLastParam2.getActive());
|
||||||
|
assertEquals("$OP_TYPE", ourLastMethod);
|
||||||
|
|
||||||
|
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
|
||||||
|
assertEquals("RET1", resp.getParameter().get(0).getName());
|
||||||
|
} finally {
|
||||||
|
status.getEntity().getContent().close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOperationWithGetUsingParams() throws Exception {
|
||||||
|
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/$OP_TYPE?PARAM1=PARAM1val");
|
||||||
|
CloseableHttpResponse status = ourClient.execute(httpGet);
|
||||||
|
try {
|
||||||
|
String response = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
ourLog.info(response);
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
status.getEntity().getContent().close();
|
||||||
|
|
||||||
|
assertNull(ourLastResourceParam);
|
||||||
|
assertEquals("PARAM1val", ourLastParam1.getValue());
|
||||||
|
|
||||||
|
assertNull(ourLastParam2);
|
||||||
|
assertEquals("$OP_TYPE", ourLastMethod);
|
||||||
|
|
||||||
|
Parameters resp = ourCtx.newXmlParser().parseResource(Parameters.class, response);
|
||||||
|
assertEquals("RET1", resp.getParameter().get(0).getName());
|
||||||
|
} finally {
|
||||||
|
status.getEntity().getContent().close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static class PatientProvider implements IResourceProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Patient> getResourceType() {
|
||||||
|
return Patient.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(name = Operation.NAME_MATCH_ALL)
|
||||||
|
public Parameters opInstance(
|
||||||
|
@ResourceParam() IBaseResource theResourceParam,
|
||||||
|
@IdParam IdType theId,
|
||||||
|
@OperationParam(name = "PARAM1") StringType theParam1,
|
||||||
|
@OperationParam(name = "PARAM2") Patient theParam2
|
||||||
|
) {
|
||||||
|
|
||||||
|
ourLastMethod = "$OP_INSTANCE";
|
||||||
|
ourLastId = theId;
|
||||||
|
ourLastParam1 = theParam1;
|
||||||
|
ourLastParam2 = theParam2;
|
||||||
|
ourLastResourceParam = (Parameters) theResourceParam;
|
||||||
|
|
||||||
|
Parameters retVal = new Parameters();
|
||||||
|
retVal.addParameter().setName("RET1").setValue(new StringType("RETVAL1"));
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Operation(name = Operation.NAME_MATCH_ALL, idempotent = true)
|
||||||
|
public Parameters opType(
|
||||||
|
@ResourceParam() IBaseResource theResourceParam,
|
||||||
|
@OperationParam(name = "PARAM1") StringType theParam1,
|
||||||
|
@OperationParam(name = "PARAM2") Patient theParam2,
|
||||||
|
@OperationParam(name = "PARAM3", min = 2, max = 5) List<StringType> theParam3,
|
||||||
|
@OperationParam(name = "PARAM4", min = 1) List<StringType> theParam4
|
||||||
|
) {
|
||||||
|
|
||||||
|
ourLastMethod = "$OP_TYPE";
|
||||||
|
ourLastParam1 = theParam1;
|
||||||
|
ourLastParam2 = theParam2;
|
||||||
|
ourLastResourceParam = (Parameters) theResourceParam;
|
||||||
|
|
||||||
|
Parameters retVal = new Parameters();
|
||||||
|
retVal.addParameter().setName("RET1").setValue(new StringType("RETVAL1"));
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static class PlainProvider {
|
||||||
|
|
||||||
|
@Operation(name = Operation.NAME_MATCH_ALL)
|
||||||
|
public Parameters opServer(
|
||||||
|
@ResourceParam() IBaseResource theResourceParam,
|
||||||
|
@OperationParam(name = "PARAM1") StringType theParam1,
|
||||||
|
@OperationParam(name = "PARAM2") Patient theParam2
|
||||||
|
) {
|
||||||
|
|
||||||
|
ourLastMethod = "$OP_SERVER";
|
||||||
|
ourLastParam1 = theParam1;
|
||||||
|
ourLastParam2 = theParam2;
|
||||||
|
ourLastResourceParam = (Parameters) theResourceParam;
|
||||||
|
|
||||||
|
Parameters retVal = new Parameters();
|
||||||
|
retVal.addParameter().setName("RET1").setValue(new StringType("RETVAL1"));
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void afterClassClearContext() throws Exception {
|
||||||
|
ourServer.stop();
|
||||||
|
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void beforeClass() throws Exception {
|
||||||
|
ourCtx = FhirContext.forR4();
|
||||||
|
ourPort = PortUtil.findFreePort();
|
||||||
|
ourServer = new Server(ourPort);
|
||||||
|
|
||||||
|
ServletHandler proxyHandler = new ServletHandler();
|
||||||
|
RestfulServer servlet = new RestfulServer(ourCtx);
|
||||||
|
|
||||||
|
servlet.setPagingProvider(new FifoMemoryPagingProvider(10).setDefaultPageSize(2));
|
||||||
|
|
||||||
|
servlet.setFhirContext(ourCtx);
|
||||||
|
servlet.setResourceProviders(new PatientProvider());
|
||||||
|
servlet.setPlainProviders(new PlainProvider());
|
||||||
|
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||||
|
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||||
|
ourServer.setHandler(proxyHandler);
|
||||||
|
ourServer.start();
|
||||||
|
|
||||||
|
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
||||||
|
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||||
|
builder.setConnectionManager(connectionManager);
|
||||||
|
ourClient = builder.build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/changes/1.0.0"
|
<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/changes/1.0.0"
|
||||||
xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 ./changes.xsd">
|
xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 ./changes.xsd">
|
||||||
<properties>
|
<properties>
|
||||||
<author>James Agnew</author>
|
<author>James Agnew</author>
|
||||||
<title>HAPI FHIR Changelog</title>
|
<title>HAPI FHIR Changelog</title>
|
||||||
|
@ -125,6 +125,22 @@
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
The FhirTerser <![CDATA[<code>getValues(...)</code>]]> methods were not properly handling modifier
|
The FhirTerser <![CDATA[<code>getValues(...)</code>]]> methods were not properly handling modifier
|
||||||
extensions for verions of FHIR prior to DSTU3. This has been corrected.
|
extensions for verions of FHIR prior to DSTU3. This has been corrected.
|
||||||
|
<action type="fix">
|
||||||
|
When updating resources in the JPA server, a bug caused index table entries to be refreshed
|
||||||
|
sometimes even though the index value hadn't changed. This issue did not cause incorrect search
|
||||||
|
results but had an effect on write performance. This has been corrected.
|
||||||
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
The @Operation annotation used to declare operations on the Plain Server now
|
||||||
|
has a wildcard constant which may be used for the operation name. This allows
|
||||||
|
you to create a server that supports operations that are not known to the
|
||||||
|
server when it starts up. This is generally not advisable but can be useful
|
||||||
|
for some circumstances.
|
||||||
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
When using an @Operation method in the Plain Server, it is now possible
|
||||||
|
to use a parameter annotated with @ResourceParam to receive the Parameters
|
||||||
|
(or other) resource supplied by the client as the request body.
|
||||||
</action>
|
</action>
|
||||||
</release>
|
</release>
|
||||||
|
|
||||||
|
@ -333,7 +349,7 @@
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
Fixed a bug when creating a custom search parameter in the JPA
|
Fixed a bug when creating a custom search parameter in the JPA
|
||||||
server: if the SearchParameter resource contained an invalid
|
server: if the SearchParameter resource contained an invalid
|
||||||
expression, create/update operations for the given resource would
|
expression, create/update operations for the given resource would
|
||||||
fail with a cryptic error. SearchParameter expressions are now
|
fail with a cryptic error. SearchParameter expressions are now
|
||||||
validated upon storage, and the SearchParameter will be rejected
|
validated upon storage, and the SearchParameter will be rejected
|
||||||
|
@ -1259,7 +1275,7 @@ ALTER TABLE hfj_res_ver ALTER COLUMN res_text DROP NOT NULL;</pre>
|
||||||
<![CDATA[<code>getCountSearchResultsUpTo()</code>]]>.
|
<![CDATA[<code>getCountSearchResultsUpTo()</code>]]>.
|
||||||
This setting governs how many search results the search
|
This setting governs how many search results the search
|
||||||
coordinator should try to find before returning an initial
|
coordinator should try to find before returning an initial
|
||||||
search response to the user, which has an effect on whether
|
search response to the user, which has an effect on whether
|
||||||
the
|
the
|
||||||
<![CDATA[<code>Bundle.total</code>]]>
|
<![CDATA[<code>Bundle.total</code>]]>
|
||||||
field is always populated in search responses. This has now
|
field is always populated in search responses. This has now
|
||||||
|
@ -1321,7 +1337,8 @@ ALTER TABLE hfj_res_ver ALTER COLUMN res_text DROP NOT NULL;</pre>
|
||||||
Michael Lawley for the pull request!
|
Michael Lawley for the pull request!
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
Add <![CDATA[<code>Prefer</code> and <code>Cache-Control</code>]]> to the list of headers which are declared as
|
Add <![CDATA[<code>Prefer</code> and <code>Cache-Control</code>]]> to the list of headers which are declared
|
||||||
|
as
|
||||||
being acceptable for CORS requests in CorsInterceptor, CLI, and JPA Example.
|
being acceptable for CORS requests in CorsInterceptor, CLI, and JPA Example.
|
||||||
Thanks to Patrick Werner for the pull request!
|
Thanks to Patrick Werner for the pull request!
|
||||||
</action>
|
</action>
|
||||||
|
@ -1831,7 +1848,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
optimize something that did not need optimizing!
|
optimize something that did not need optimizing!
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
A new config property has been added to the JPA seerver DaoConfig called "setAutoCreatePlaceholderReferenceTargets".
|
A new config property has been added to the JPA seerver DaoConfig called
|
||||||
|
"setAutoCreatePlaceholderReferenceTargets".
|
||||||
This property causes references to unknown resources in created/updated resources to have a placeholder
|
This property causes references to unknown resources in created/updated resources to have a placeholder
|
||||||
target resource automatically created.
|
target resource automatically created.
|
||||||
</action>
|
</action>
|
||||||
|
@ -2131,7 +2149,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
Add a utility method to JPA server:
|
Add a utility method to JPA server:
|
||||||
<![CDATA[<code>IFhirResourceDao#removeTag(IIdType, TagTypeEnum, String, String)</code>]]>. This allows client code to remove tags
|
<![CDATA[<code>IFhirResourceDao#removeTag(IIdType, TagTypeEnum, String, String)</code>]]>. This allows
|
||||||
|
client code to remove tags
|
||||||
from a resource without having a servlet request object in context.
|
from a resource without having a servlet request object in context.
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
|
@ -2383,7 +2402,7 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
</ul>
|
</ul>
|
||||||
]]>
|
]]>
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
Fix issue in AuthorizationIntetceptor where
|
Fix issue in AuthorizationIntetceptor where
|
||||||
transactions are blocked even when they
|
transactions are blocked even when they
|
||||||
should not be
|
should not be
|
||||||
|
@ -2500,11 +2519,11 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
<li>Normalization of properties across all three generic tasks</li>
|
<li>Normalization of properties across all three generic tasks</li>
|
||||||
</ul>
|
</ul>
|
||||||
]]>
|
]]>
|
||||||
</action>
|
</action>
|
||||||
<action type="fix" issue="523">
|
<action type="fix" issue="523">
|
||||||
Fix ordering of validator property handling when an element
|
Fix ordering of validator property handling when an element
|
||||||
has a name that is similar to a shorter name[x] style name.
|
has a name that is similar to a shorter name[x] style name.
|
||||||
Thanks to CarthageKing for the pull request!
|
Thanks to CarthageKing for the pull request!
|
||||||
</action>
|
</action>
|
||||||
<action type="add" issue="510">
|
<action type="add" issue="510">
|
||||||
Add a docker configuration to the hapi-fhir-jpaservr-example
|
Add a docker configuration to the hapi-fhir-jpaservr-example
|
||||||
|
@ -2525,8 +2544,10 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
<![CDATA[<code>IHttpRequest</code>]]> class: "bufferEntitity" should be "bufferEntity".
|
<![CDATA[<code>IHttpRequest</code>]]> class: "bufferEntitity" should be "bufferEntity".
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
ErrorHandler is now called (resulting in a warning by default, but can also be an exception) when arsing JSON if
|
ErrorHandler is now called (resulting in a warning by default, but can also be an exception) when arsing
|
||||||
the resource ID is not a JSON string, or an object is found where an array is expected (e.g. repeating field). Thanks
|
JSON if
|
||||||
|
the resource ID is not a JSON string, or an object is found where an array is expected (e.g. repeating
|
||||||
|
field). Thanks
|
||||||
to Jenni Syed of Cerner for providing a test case!
|
to Jenni Syed of Cerner for providing a test case!
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
|
@ -2649,8 +2670,10 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
]]>
|
]]>
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
Fix a fairly significant issue in JPA Server when using the <![CDATA[<code>DatabaseBackedPagingProvider</code>]]>: When paging over the results
|
Fix a fairly significant issue in JPA Server when using the
|
||||||
of a search / $everything operation, under certain circumstances resources may be missing from the last page of results
|
<![CDATA[<code>DatabaseBackedPagingProvider</code>]]>: When paging over the results
|
||||||
|
of a search / $everything operation, under certain circumstances resources may be missing from the last page
|
||||||
|
of results
|
||||||
that is returned. Thanks to David Hay for reporting!
|
that is returned. Thanks to David Hay for reporting!
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
|
@ -2805,7 +2828,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
Kevin Tallevi for finding this!
|
Kevin Tallevi for finding this!
|
||||||
</action>
|
</action>
|
||||||
<action type="fix" issue="411">
|
<action type="fix" issue="411">
|
||||||
Fix #411 - Searching by <![CDATA[<code>POST [base]/_search</code>]]> with urlencoded parameters doesn't work correctly if
|
Fix #411 - Searching by <![CDATA[<code>POST [base]/_search</code>]]> with urlencoded parameters doesn't work
|
||||||
|
correctly if
|
||||||
interceptors are accessing the parameters and there is are also
|
interceptors are accessing the parameters and there is are also
|
||||||
parameters on the URL. Thanks to Jim Steel for reporting!
|
parameters on the URL. Thanks to Jim Steel for reporting!
|
||||||
</action>
|
</action>
|
||||||
|
@ -2918,7 +2942,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
Both client and server now support the new Content Types decided in
|
Both client and server now support the new Content Types decided in
|
||||||
<![CDATA[<a href="http://gforge.hl7.org/gf/project/fhir/tracker/?action=TrackerItemEdit&tracker_id=677&tracker_item_id=10199">FHIR #10199</a>]]>.
|
<![CDATA[<a href="http://gforge.hl7.org/gf/project/fhir/tracker/?action=TrackerItemEdit&tracker_id=677&tracker_item_id=10199">FHIR #10199</a>]]>
|
||||||
|
.
|
||||||
<![CDATA[<br/><br/>]]>
|
<![CDATA[<br/><br/>]]>
|
||||||
This means that the server now supports
|
This means that the server now supports
|
||||||
<![CDATA[<code>application/fhir+xml</code> and <code>application/fhir+json</code>]]>
|
<![CDATA[<code>application/fhir+xml</code> and <code>application/fhir+json</code>]]>
|
||||||
|
@ -3030,7 +3055,7 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
Fix a regression when parsing resources that have contained
|
Fix a regression when parsing resources that have contained
|
||||||
resources, where the reference in the outer resource which
|
resources, where the reference in the outer resource which
|
||||||
links to the contained resource sometimes did does not get
|
links to the contained resource sometimes did does not get
|
||||||
populated with the actual target resource instance. Thanks to
|
populated with the actual target resource instance. Thanks to
|
||||||
Neal Acharya for reporting!
|
Neal Acharya for reporting!
|
||||||
|
@ -3395,7 +3420,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
reporting!
|
reporting!
|
||||||
</action>
|
</action>
|
||||||
<action type="fix" issue="371">
|
<action type="fix" issue="371">
|
||||||
Update STU3 client and server to use the new sort parameter style (param1,-param2,param). Thanks to GitHub user @euz1e4r for
|
Update STU3 client and server to use the new sort parameter style (param1,-param2,param). Thanks to GitHub
|
||||||
|
user @euz1e4r for
|
||||||
reporting!
|
reporting!
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
|
@ -3738,7 +3764,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
REST Server responded to HTTP OPTIONS requests with
|
REST Server responded to HTTP OPTIONS requests with
|
||||||
any URI as being a request for the server's
|
any URI as being a request for the server's
|
||||||
Conformance statement. This is incorrect, as only
|
Conformance statement. This is incorrect, as only
|
||||||
a request for <![CDATA[<code>OPTIONS [base url]</code>]]> should be treated as such. Thanks to Michael Lawley for reporting!
|
a request for <![CDATA[<code>OPTIONS [base url]</code>]]> should be treated as such. Thanks to Michael
|
||||||
|
Lawley for reporting!
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
REST annotation style client was not able to handle extended operations
|
REST annotation style client was not able to handle extended operations
|
||||||
|
@ -4200,7 +4227,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
In server, if a client request is received and it has an Accept header indicating
|
In server, if a client request is received and it has an Accept header indicating
|
||||||
that it supports both XML and JSON with equal weight, the server's default is used instead of the first entry in the list.
|
that it supports both XML and JSON with equal weight, the server's default is used instead of the first
|
||||||
|
entry in the list.
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
JPA server now supports searching with sort by token, quantity,
|
JPA server now supports searching with sort by token, quantity,
|
||||||
|
@ -4257,7 +4285,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
to Alexander Kley for the fix!
|
to Alexander Kley for the fix!
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
JPA server now supports $everything on Patient and Encounter types (patient and encounter instance was already supported)
|
JPA server now supports $everything on Patient and Encounter types (patient and encounter instance was
|
||||||
|
already supported)
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
Generic client operation invocations now
|
Generic client operation invocations now
|
||||||
|
@ -4404,7 +4433,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
</action>
|
</action>
|
||||||
<action type="fix" issue="198">
|
<action type="fix" issue="198">
|
||||||
JPA server sorting often returned unexpected orders when multiple
|
JPA server sorting often returned unexpected orders when multiple
|
||||||
indexes of the same type were found on the same resource (e.g. multiple string indexed fields). Thanks to Travis Cummings for reporting!
|
indexes of the same type were found on the same resource (e.g. multiple string indexed fields). Thanks to
|
||||||
|
Travis Cummings for reporting!
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
Add another method to IServerInterceptor which converts an exception generated on the server
|
Add another method to IServerInterceptor which converts an exception generated on the server
|
||||||
|
@ -4513,10 +4543,13 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
JPA server did not correctly index search parameters
|
JPA server did not correctly index search parameters
|
||||||
of type "URI". Thanks to David Hay for reporting! Note that if you are using the JPA server, this change means that
|
of type "URI". Thanks to David Hay for reporting! Note that if you are using the JPA server, this change
|
||||||
there are two new tables added to the database schema. Updating existing resources in the database may fail unless you
|
means that
|
||||||
|
there are two new tables added to the database schema. Updating existing resources in the database may fail
|
||||||
|
unless you
|
||||||
set default values for the resource
|
set default values for the resource
|
||||||
table by issuing a SQL command similar to the following (false may be 0 or something else, depending on the database platform in use)
|
table by issuing a SQL command similar to the following (false may be 0 or something else, depending on the
|
||||||
|
database platform in use)
|
||||||
<![CDATA[<br/><code>update hfj_resource set sp_coords_present = false;<br/>
|
<![CDATA[<br/><code>update hfj_resource set sp_coords_present = false;<br/>
|
||||||
update hfj_resource set sp_uri_present = false;</code>]]>
|
update hfj_resource set sp_uri_present = false;</code>]]>
|
||||||
</action>
|
</action>
|
||||||
|
@ -4561,7 +4594,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
in history
|
in history
|
||||||
</action>
|
</action>
|
||||||
<action type="fix" issue="222">
|
<action type="fix" issue="222">
|
||||||
JPA server returned deleted resources in search results when using the _tag, _id, _profile, or _security search parameters
|
JPA server returned deleted resources in search results when using the _tag, _id, _profile, or _security
|
||||||
|
search parameters
|
||||||
</action>
|
</action>
|
||||||
<action type="fix" issue="223">
|
<action type="fix" issue="223">
|
||||||
Fix issue with build on Windows. Thanks to Bryce van Dyk for the pull request!
|
Fix issue with build on Windows. Thanks to Bryce van Dyk for the pull request!
|
||||||
|
@ -4581,7 +4615,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
Claude Nanjo for finding this.
|
Claude Nanjo for finding this.
|
||||||
</action>
|
</action>
|
||||||
<action type="fix" issue="164">
|
<action type="fix" issue="164">
|
||||||
Correct performance issue with :missing=true search requests where the parameter is a resource link. Thanks to wanghaisheng for all his help in testing this.
|
Correct performance issue with :missing=true search requests where the parameter is a resource link. Thanks
|
||||||
|
to wanghaisheng for all his help in testing this.
|
||||||
</action>
|
</action>
|
||||||
<action type="fix" issue="149">
|
<action type="fix" issue="149">
|
||||||
The self link in the Bundle returned by searches on the server does not respect the
|
The self link in the Bundle returned by searches on the server does not respect the
|
||||||
|
@ -4598,7 +4633,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
Peter Girard for reporting!
|
Peter Girard for reporting!
|
||||||
</action>
|
</action>
|
||||||
<action type="add" issue="170">
|
<action type="add" issue="170">
|
||||||
Add better addXXX() methods to structures, which take the datatype being added as a parameter. Thanks to Claude Nanjo for the
|
Add better addXXX() methods to structures, which take the datatype being added as a parameter. Thanks to
|
||||||
|
Claude Nanjo for the
|
||||||
suggestion!
|
suggestion!
|
||||||
</action>
|
</action>
|
||||||
<action type="add" issue="152">
|
<action type="add" issue="152">
|
||||||
|
@ -4668,7 +4704,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
q values specifying order of preference. Previously the q value was ignored.
|
q values specifying order of preference. Previously the q value was ignored.
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
Server in DSTU2 mode now indicates that whether it has support for Transaction operation or not. Thanks to Kevin Paschke for pointing out that this wasn't working!
|
Server in DSTU2 mode now indicates that whether it has support for Transaction operation or not. Thanks to
|
||||||
|
Kevin Paschke for pointing out that this wasn't working!
|
||||||
</action>
|
</action>
|
||||||
<action type="add" issue="166">
|
<action type="add" issue="166">
|
||||||
Questionnaire.title now gets correctly indexed in JPA server (it has no path, so it is a special case)
|
Questionnaire.title now gets correctly indexed in JPA server (it has no path, so it is a special case)
|
||||||
|
@ -4754,7 +4791,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
McKenzie for reporting!
|
McKenzie for reporting!
|
||||||
</action>
|
</action>
|
||||||
<action type="fix" issue="128">
|
<action type="fix" issue="128">
|
||||||
Fix regression in 0.9 - Server responds with an HTTP 500 and a NullPointerException instead of an HTTP 400 and a useful error message if the client requests an unknown resource type
|
Fix regression in 0.9 - Server responds with an HTTP 500 and a NullPointerException instead of an HTTP 400
|
||||||
|
and a useful error message if the client requests an unknown resource type
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
Add support for
|
Add support for
|
||||||
|
@ -4926,7 +4964,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
the patch!
|
the patch!
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
Transaction server operations incorrectly used the "Accept" header instead of the "Content-Type" header to determine the
|
Transaction server operations incorrectly used the "Accept" header instead of the "Content-Type" header to
|
||||||
|
determine the
|
||||||
POST request encoding. Thanks to Rene Spronk for providing a test case!
|
POST request encoding. Thanks to Rene Spronk for providing a test case!
|
||||||
</action>
|
</action>
|
||||||
</release>
|
</release>
|
||||||
|
@ -5014,7 +5053,7 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
RestfulClientConfig. Thanks to Grahame Grieve for the suggestion!
|
RestfulClientConfig. Thanks to Grahame Grieve for the suggestion!
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
JPA module now supports deleting resource via transaction
|
JPA module now supports deleting resource via transaction
|
||||||
</action>
|
</action>
|
||||||
<action type="fix" issue="97" due-to="twilson650">
|
<action type="fix" issue="97" due-to="twilson650">
|
||||||
DateClientParam#second() incorrectly used DAY precision instead
|
DateClientParam#second() incorrectly used DAY precision instead
|
||||||
|
@ -5042,7 +5081,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
Server requests for Binary resources where the client has explicitly requested XML or JSON responses
|
Server requests for Binary resources where the client has explicitly requested XML or JSON responses
|
||||||
(either with a <![CDATA[<code>_format</code>]]> URL parameter, or an <![CDATA[<code>Accept</code>]]> request header)
|
(either with a <![CDATA[<code>_format</code>]]> URL parameter, or an <![CDATA[<code>Accept</code>]]> request
|
||||||
|
header)
|
||||||
will be responded to using the Binary FHIR resource type instead of as Binary blobs. This is
|
will be responded to using the Binary FHIR resource type instead of as Binary blobs. This is
|
||||||
in accordance with the recommended behaviour in the FHIR specification.
|
in accordance with the recommended behaviour in the FHIR specification.
|
||||||
</action>
|
</action>
|
||||||
|
@ -5080,7 +5120,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
to baopingle for reporting and providing a test case!
|
to baopingle for reporting and providing a test case!
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
Sorting is now supported in the Web Testing UI (previously a button existed for sorting, but it didn't do anything)
|
Sorting is now supported in the Web Testing UI (previously a button existed for sorting, but it didn't do
|
||||||
|
anything)
|
||||||
</action>
|
</action>
|
||||||
<action type="add" issue="111">
|
<action type="add" issue="111">
|
||||||
Server will no longer include stack traces in the OperationOutcome returned to the client
|
Server will no longer include stack traces in the OperationOutcome returned to the client
|
||||||
|
@ -5354,7 +5395,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
for reporting this!
|
for reporting this!
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
XHTML (in narratives) containing escapable characters (e.g. < or ") will now always have those characters
|
XHTML (in narratives) containing escapable characters (e.g. < or ") will now always have those
|
||||||
|
characters
|
||||||
escaped properly in encoded messages.
|
escaped properly in encoded messages.
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
|
@ -5404,7 +5446,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
Thanks to Bill de Beaubien for reporting!
|
Thanks to Bill de Beaubien for reporting!
|
||||||
</action>
|
</action>
|
||||||
<action type="update">
|
<action type="update">
|
||||||
Documentation on contained resources contained a typo and did not actually produce contained resources. Thanks
|
Documentation on contained resources contained a typo and did not actually produce contained resources.
|
||||||
|
Thanks
|
||||||
to David Hay of Orion Health for reporting!
|
to David Hay of Orion Health for reporting!
|
||||||
</action>
|
</action>
|
||||||
<action type="add" issue="31" dev="preston">
|
<action type="add" issue="31" dev="preston">
|
||||||
|
@ -5423,7 +5466,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
Petro Mykhailysyn for the pull request!
|
Petro Mykhailysyn for the pull request!
|
||||||
</action>
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="0.6" date="2014-09-08" description="This release brings a number of new features and bug fixes!">
|
<release version="0.6" date="2014-09-08"
|
||||||
|
description="This release brings a number of new features and bug fixes!">
|
||||||
<!--
|
<!--
|
||||||
<action type="add">
|
<action type="add">
|
||||||
Allow generic client ... OAUTH
|
Allow generic client ... OAUTH
|
||||||
|
@ -5522,13 +5566,15 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
Rename NotImpementedException to NotImplementedException (to correct typo)
|
Rename NotImpementedException to NotImplementedException (to correct typo)
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
Server setUseBrowserFriendlyContentType setting also respected for errors (e.g. OperationOutcome with 4xx/5xx status)
|
Server setUseBrowserFriendlyContentType setting also respected for errors (e.g. OperationOutcome with
|
||||||
|
4xx/5xx status)
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
Fix performance issue in date/time datatypes where pattern matchers were not static
|
Fix performance issue in date/time datatypes where pattern matchers were not static
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
Server now gives a more helpful error message if a @Read method has a search parameter (which is invalid, but
|
Server now gives a more helpful error message if a @Read method has a search parameter (which is invalid,
|
||||||
|
but
|
||||||
previously lead to a very unhelpful error message). Thanks to Tahura Chaudhry of UHN for reporting!
|
previously lead to a very unhelpful error message). Thanks to Tahura Chaudhry of UHN for reporting!
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
|
@ -5601,20 +5647,21 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
for configurable logging, capturing requests and responses, and HTTP basic auth.
|
for configurable logging, capturing requests and responses, and HTTP basic auth.
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
Transaction client invocations with XML encoding were using the wrong content type ("application/xml+fhir" instead
|
Transaction client invocations with XML encoding were using the wrong content type ("application/xml+fhir"
|
||||||
|
instead
|
||||||
of the correct "application/atom+xml"). Thanks to David Hay of Orion Health for surfacing this one!
|
of the correct "application/atom+xml"). Thanks to David Hay of Orion Health for surfacing this one!
|
||||||
</action>
|
</action>
|
||||||
<action type="add">
|
<action type="add">
|
||||||
Bundle entries now support a link type of "search". Thanks to David Hay for the suggestion!
|
Bundle entries now support a link type of "search". Thanks to David Hay for the suggestion!
|
||||||
</action>
|
</action>
|
||||||
<action type="add" issue="1">
|
<action type="add" issue="1">
|
||||||
If a client receives a non 2xx response (e.g. HTTP 500) and the response body is a text/plain message or
|
If a client receives a non 2xx response (e.g. HTTP 500) and the response body is a text/plain message or
|
||||||
an OperationOutcome resource, include the message in the exception message so that it will be
|
an OperationOutcome resource, include the message in the exception message so that it will be
|
||||||
more conveniently displayed in logs and other places. Thanks to Neal Acharya for the suggestion!
|
more conveniently displayed in logs and other places. Thanks to Neal Acharya for the suggestion!
|
||||||
</action>
|
</action>
|
||||||
<action type="add" issue="2">
|
<action type="add" issue="2">
|
||||||
Read invocations in the client now process the "Content-Location" header and use it to
|
Read invocations in the client now process the "Content-Location" header and use it to
|
||||||
populate the ID of the returned resource. Thanks to Neal Acharya for the suggestion!
|
populate the ID of the returned resource. Thanks to Neal Acharya for the suggestion!
|
||||||
</action>
|
</action>
|
||||||
<action type="fix" issue="3">
|
<action type="fix" issue="3">
|
||||||
Fix issue where vread invocations on server incorrectly get routed to instance history method if one is
|
Fix issue where vread invocations on server incorrectly get routed to instance history method if one is
|
||||||
|
@ -5639,7 +5686,7 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
space (e.g. a WAR file with a space in the name) failed to work correctly.
|
space (e.g. a WAR file with a space in the name) failed to work correctly.
|
||||||
Thanks to David Hay of Orion for reporting this!
|
Thanks to David Hay of Orion for reporting this!
|
||||||
</action>
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="0.4" date="2014-07-13">
|
<release version="0.4" date="2014-07-13">
|
||||||
<action type="add">
|
<action type="add">
|
||||||
<![CDATA[<b>BREAKING CHANGE:</b>]]>: IdDt has been modified so that it
|
<![CDATA[<b>BREAKING CHANGE:</b>]]>: IdDt has been modified so that it
|
||||||
|
@ -5680,7 +5727,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
Support for Query resources fixed (in parser)
|
Support for Query resources fixed (in parser)
|
||||||
</action>
|
</action>
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
Nested contained resources (e.g. encoding a resource with a contained resource that itself contains a resource)
|
Nested contained resources (e.g. encoding a resource with a contained resource that itself contains a
|
||||||
|
resource)
|
||||||
now parse and encode correctly, meaning that all contained resources are placed in the "contained" element
|
now parse and encode correctly, meaning that all contained resources are placed in the "contained" element
|
||||||
of the root resource, and the parser looks in the root resource for all container levels when stitching
|
of the root resource, and the parser looks in the root resource for all container levels when stitching
|
||||||
contained resources back together.
|
contained resources back together.
|
||||||
|
@ -5713,7 +5761,8 @@ Bundle bundle = client.search().forResource(Patient.class)
|
||||||
Support added for deleted-entry by/name, by/email, and comment from Tombstones spec
|
Support added for deleted-entry by/name, by/email, and comment from Tombstones spec
|
||||||
</action>
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="0.3" date="2014-05-12" description="This release corrects lots of bugs and introduces the fluent client mode">
|
<release version="0.3" date="2014-05-12"
|
||||||
|
description="This release corrects lots of bugs and introduces the fluent client mode">
|
||||||
</release>
|
</release>
|
||||||
<release version="0.2" date="2014-04-23">
|
<release version="0.2" date="2014-04-23">
|
||||||
</release>
|
</release>
|
||||||
|
|
Loading…
Reference in New Issue