Properly support chains in JPA conditional URLs

This commit is contained in:
James Agnew 2015-10-04 15:38:58 -04:00
parent 5e2d94d745
commit 43aad1eb98
18 changed files with 924 additions and 109 deletions

View File

@ -84,6 +84,7 @@ import ca.uhn.fhir.rest.param.ResourceParameter.Mode;
import ca.uhn.fhir.rest.param.StringAndListParam; import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.TokenAndListParam; import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TransactionParameter; import ca.uhn.fhir.rest.param.TransactionParameter;
import ca.uhn.fhir.rest.param.UriAndListParam;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider; import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider;
@ -630,6 +631,9 @@ public class MethodUtil {
case TOKEN: case TOKEN:
binder = new QueryParameterAndBinder(TokenAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList()); binder = new QueryParameterAndBinder(TokenAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList());
break; break;
case URI:
binder = new QueryParameterAndBinder(UriAndListParam.class, Collections.<Class<? extends IQueryParameterType>> emptyList());
break;
} }
return binder.parse(theUnqualifiedParamName, theParameters); return binder.parse(theUnqualifiedParamName, theParameters);

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.cli; package ca.uhn.fhir.cli;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -28,7 +29,6 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
import ca.uhn.fhir.model.dstu2.resource.Bundle.EntryRequest; import ca.uhn.fhir.model.dstu2.resource.Bundle.EntryRequest;
import ca.uhn.fhir.model.dstu2.resource.DataElement;
import ca.uhn.fhir.model.dstu2.resource.SearchParameter; import ca.uhn.fhir.model.dstu2.resource.SearchParameter;
import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum; import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
@ -61,9 +61,14 @@ public class ExampleDataUploader extends BaseCommand {
opt.setRequired(false); opt.setRequired(false);
options.addOption(opt); options.addOption(opt);
opt = new Option("t", "target", true, "Base URL for the target server"); opt = new Option("t", "target", true, "Base URL for the target server (e.g. \"http://example.com/fhir\")");
opt.setRequired(true); opt.setRequired(true);
options.addOption(opt); options.addOption(opt);
opt = new Option("l", "limit", true, "Sets a limit to the number of resources the uploader will try to upload");
opt.setRequired(false);
options.addOption(opt);
return options; return options;
} }
@ -76,6 +81,15 @@ public class ExampleDataUploader extends BaseCommand {
} else if (targetServer.startsWith("http") == false) { } else if (targetServer.startsWith("http") == false) {
throw new ParseException("Invalid target server specified, must begin with 'http'"); throw new ParseException("Invalid target server specified, must begin with 'http'");
} }
Integer limit = null;
String limitString = theCommandLine.getOptionValue('l');
if (isNotBlank(limitString)) {
try {
limit = Integer.parseInt(limitString);
} catch (NumberFormatException e) {
throw new ParseException("Invalid number for limit (-l) option, must be a number: " + limitString);
}
}
FhirContext ctx = FhirContext.forDstu2(); FhirContext ctx = FhirContext.forDstu2();
String specUrl = "http://hl7.org/fhir/" + specVersion + "/examples-json.zip"; String specUrl = "http://hl7.org/fhir/" + specVersion + "/examples-json.zip";
@ -100,7 +114,13 @@ public class ExampleDataUploader extends BaseCommand {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
int count = 0;
while (true) { while (true) {
count++;
if (limit != null && count > limit) {
break;
}
ZipEntry nextEntry = zis.getNextEntry(); ZipEntry nextEntry = zis.getNextEntry();
if (nextEntry == null) { if (nextEntry == null) {
break; break;

View File

@ -23,8 +23,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.Normalizer; import java.text.Normalizer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -93,12 +91,14 @@ import ca.uhn.fhir.jpa.entity.TagDefinition;
import ca.uhn.fhir.jpa.entity.TagTypeEnum; import ca.uhn.fhir.jpa.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.util.StopWatch; import ca.uhn.fhir.jpa.util.StopWatch;
import ca.uhn.fhir.model.api.IQueryParameterAnd; import ca.uhn.fhir.model.api.IQueryParameterAnd;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.Tag; import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.base.composite.BaseCodingDt; import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import ca.uhn.fhir.model.dstu.resource.BaseResource;
import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.InstantDt;
@ -110,8 +110,13 @@ import ca.uhn.fhir.rest.method.MethodUtil;
import ca.uhn.fhir.rest.method.QualifiedParamList; import ca.uhn.fhir.rest.method.QualifiedParamList;
import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.method.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.UriAndListParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.IBundleProvider;
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;
@ -120,9 +125,36 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.FhirTerser; import ca.uhn.fhir.util.FhirTerser;
import net.sourceforge.cobertura.CoverageIgnore;
public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao { public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
/**
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(Map)}
*/
protected static final Map<String, Class<? extends IQueryParameterType>> RESOURCE_META_PARAMS;
/**
* These are parameters which are supported by {@link BaseHapiFhirResourceDao#searchForIds(Map)}
*/
protected static final Map<String, Class<? extends IQueryParameterAnd<?>>> RESOURCE_META_AND_PARAMS;
static {
Map<String, Class<? extends IQueryParameterType>> resourceMetaParams = new HashMap<String, Class<? extends IQueryParameterType>>();
Map<String, Class<? extends IQueryParameterAnd<?>>> resourceMetaAndParams = new HashMap<String, Class<? extends IQueryParameterAnd<?>>>();
resourceMetaParams.put(BaseResource.SP_RES_ID, StringParam.class);
resourceMetaAndParams.put(BaseResource.SP_RES_ID, StringAndListParam.class);
resourceMetaParams.put(BaseResource.SP_RES_LANGUAGE, StringParam.class);
resourceMetaAndParams.put(BaseResource.SP_RES_LANGUAGE, StringAndListParam.class);
resourceMetaParams.put(Constants.PARAM_TAG, TokenParam.class);
resourceMetaAndParams.put(Constants.PARAM_TAG, TokenAndListParam.class);
resourceMetaParams.put(Constants.PARAM_PROFILE, UriParam.class);
resourceMetaAndParams.put(Constants.PARAM_PROFILE, UriAndListParam.class);
resourceMetaParams.put(Constants.PARAM_SECURITY, TokenParam.class);
resourceMetaAndParams.put(Constants.PARAM_SECURITY, TokenAndListParam.class);
RESOURCE_META_PARAMS = Collections.unmodifiableMap(resourceMetaParams);
RESOURCE_META_AND_PARAMS = Collections.unmodifiableMap(resourceMetaAndParams);
}
public static final long INDEX_STATUS_INDEXED = Long.valueOf(1L); public static final long INDEX_STATUS_INDEXED = Long.valueOf(1L);
public static final long INDEX_STATUS_INDEXING_FAILED = Long.valueOf(2L); public static final long INDEX_STATUS_INDEXING_FAILED = Long.valueOf(2L);
public static final String NS_JPA_PROFILE = "https://github.com/jamesagnew/hapi-fhir/ns/jpa/profile"; public static final String NS_JPA_PROFILE = "https://github.com/jamesagnew/hapi-fhir/ns/jpa/profile";
@ -703,6 +735,30 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return false; return false;
} }
@CoverageIgnore
protected static IQueryParameterAnd<?> newInstanceAnd(String chain) {
IQueryParameterAnd<?> type;
Class<? extends IQueryParameterAnd<?>> clazz = RESOURCE_META_AND_PARAMS.get(chain);
try {
type = clazz.newInstance();
} catch (Exception e) {
throw new InternalErrorException("Failure creating instance of " + clazz, e);
}
return type;
}
@CoverageIgnore
protected static IQueryParameterType newInstanceType(String chain) {
IQueryParameterType type;
Class<? extends IQueryParameterType> clazz = RESOURCE_META_PARAMS.get(chain);
try {
type = clazz.newInstance();
} catch (Exception e) {
throw new InternalErrorException("Failure creating instance of " + clazz, e);
}
return type;
}
protected <R extends IResource> Set<Long> processMatchUrl(String theMatchUrl, Class<R> theResourceType) { protected <R extends IResource> Set<Long> processMatchUrl(String theMatchUrl, Class<R> theResourceType) {
RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(theResourceType); RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(theResourceType);
@ -745,13 +801,13 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
String paramName = next.getName(); String paramName = next.getName();
String qualifier = null; String qualifier = null;
for (int i = 0; i < paramMap.size(); i++) { for (int i = 0; i < paramName.length(); i++) {
switch (paramName.charAt(i)) { switch (paramName.charAt(i)) {
case '.': case '.':
case ':': case ':':
qualifier = paramName.substring(i); qualifier = paramName.substring(i);
paramName = paramName.substring(0, i); paramName = paramName.substring(0, i);
i = Integer.MAX_VALUE; i = Integer.MAX_VALUE - 1;
break; break;
} }
} }
@ -787,10 +843,16 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
continue; continue;
} }
if (nextParamName.startsWith("_")) { if (RESOURCE_META_PARAMS.containsKey(nextParamName)) {
continue; if (isNotBlank(paramList.get(0).getQualifier()) && paramList.get(0).getQualifier().startsWith(".")) {
throw new InvalidRequestException("Invalid parameter chain: " + nextParamName + paramList.get(0).getQualifier());
} }
IQueryParameterAnd<?> type = newInstanceAnd(nextParamName);
type.setValuesAsQueryTokens((paramList));
paramMap.add(nextParamName, type);
} else if (nextParamName.startsWith("_")) {
// ignore these since they aren't search params (e.g. _sort)
} else {
RuntimeSearchParam paramDef = resourceDef.getSearchParam(nextParamName); RuntimeSearchParam paramDef = resourceDef.getSearchParam(nextParamName);
if (paramDef == null) { if (paramDef == null) {
throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName); throw new InvalidRequestException("Failed to parse match URL[" + theMatchUrl + "] - Resource type " + resourceDef.getName() + " does not have a parameter with name: " + nextParamName);
@ -799,6 +861,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
IQueryParameterAnd<?> param = MethodUtil.parseQueryParams(paramDef, nextParamName, paramList); IQueryParameterAnd<?> param = MethodUtil.parseQueryParams(paramDef, nextParamName, paramList);
paramMap.add(nextParamName, param); paramMap.add(nextParamName, param);
} }
}
return paramMap; return paramMap;
} }

View File

@ -656,8 +656,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
} }
boolean foundChainMatch = false; boolean foundChainMatch = false;
for (Class<? extends IBaseResource> nextType : resourceTypes) {
RuntimeResourceDefinition typeDef = getContext().getResourceDefinition(nextType);
String chain = ref.getChain(); String chain = ref.getChain();
String remainingChain = null; String remainingChain = null;
@ -667,29 +665,48 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
chain = chain.substring(0, chainDotIndex); chain = chain.substring(0, chainDotIndex);
} }
RuntimeSearchParam param = typeDef.getSearchParam(chain); for (Class<? extends IBaseResource> nextType : resourceTypes) {
RuntimeResourceDefinition typeDef = getContext().getResourceDefinition(nextType);
IFhirResourceDao<?> dao = getDao(nextType);
if (dao == null) {
ourLog.debug("Don't have a DAO for type {}", nextType.getSimpleName());
continue;
}
int qualifierIndex = chain.indexOf(':');
String qualifier = null;
if (qualifierIndex != -1) {
qualifier = chain.substring(qualifierIndex);
chain = chain.substring(0, qualifierIndex);
}
boolean isMeta = RESOURCE_META_PARAMS.containsKey(chain);
RuntimeSearchParam param = null;
if (!isMeta) {
param = typeDef.getSearchParam(chain);
if (param == null) { if (param == null) {
ourLog.debug("Type {} doesn't have search param {}", nextType.getSimpleName(), param); ourLog.debug("Type {} doesn't have search param {}", nextType.getSimpleName(), param);
continue; continue;
} }
IFhirResourceDao<?> dao = getDao(nextType);
if (dao == null) {
ourLog.debug("Don't have a DAO for type {}", nextType.getSimpleName(), param);
continue;
} }
IQueryParameterType chainValue; IQueryParameterType chainValue;
if (remainingChain != null) { if (remainingChain != null) {
if (param.getParamType() != RestSearchParameterTypeEnum.REFERENCE) { if (param == null || param.getParamType() != RestSearchParameterTypeEnum.REFERENCE) {
ourLog.debug("Type {} parameter {} is not a reference, can not chain {}", new Object[] { nextType.getSimpleName(), chain, remainingChain }); ourLog.debug("Type {} parameter {} is not a reference, can not chain {}", new Object[] { nextType.getSimpleName(), chain, remainingChain });
continue; continue;
} }
chainValue = new ReferenceParam(); chainValue = new ReferenceParam();
chainValue.setValueAsQueryToken(null, resourceId); chainValue.setValueAsQueryToken(qualifier, resourceId);
((ReferenceParam) chainValue).setChain(remainingChain); ((ReferenceParam) chainValue).setChain(remainingChain);
} else if (isMeta) {
IQueryParameterType type = newInstanceType(chain);
type.setValueAsQueryToken(qualifier, resourceId);
chainValue = type;
} else { } else {
chainValue = toParameterType(param, resourceId); chainValue = toParameterType(param, qualifier, resourceId);
} }
foundChainMatch = true; foundChainMatch = true;
@ -729,6 +746,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
return new HashSet<Long>(q.getResultList()); return new HashSet<Long>(q.getResultList());
} }
private Set<Long> addPredicateString(String theParamName, Set<Long> thePids, List<? extends IQueryParameterType> theList) { private Set<Long> addPredicateString(String theParamName, Set<Long> thePids, List<? extends IQueryParameterType> theList) {
if (theList == null || theList.isEmpty()) { if (theList == null || theList.isEmpty()) {
return thePids; return thePids;
@ -783,7 +801,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
} else if (Constants.PARAM_SECURITY.equals(theParamName)) { } else if (Constants.PARAM_SECURITY.equals(theParamName)) {
tagType = TagTypeEnum.SECURITY_LABEL; tagType = TagTypeEnum.SECURITY_LABEL;
} else { } else {
throw new IllegalArgumentException("Paramname: " + theParamName); // shouldn't happen throw new IllegalArgumentException("Param name: " + theParamName); // shouldn't happen
} }
for (List<? extends IQueryParameterType> nextAndParams : theList) { for (List<? extends IQueryParameterType> nextAndParams : theList) {
@ -1358,7 +1376,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
} }
ourLog.info("Processed delete on {} (matched {} resource(s)) in {}ms", new Object[] {theUrl, resource.size(), w.getMillisAndRestart()}); ourLog.info("Processed delete on {} (matched {} resource(s)) in {}ms", new Object[] { theUrl, resource.size(), w.getMillisAndRestart() });
return new DaoMethodOutcome(); return new DaoMethodOutcome();
} }
@ -2114,16 +2132,16 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
List<Long> pidsSubList = pids.subList(theFromIndex, theToIndex); List<Long> pidsSubList = pids.subList(theFromIndex, theToIndex);
// Load includes // Load includes
if (theParams.getEverythingMode()==null) { if (theParams.getEverythingMode() == null) {
pidsSubList = new ArrayList<Long>(pidsSubList); pidsSubList = new ArrayList<Long>(pidsSubList);
revIncludedPids.addAll(loadReverseIncludes(pidsSubList, theParams.getIncludes(), false, null)); revIncludedPids.addAll(loadReverseIncludes(pidsSubList, theParams.getIncludes(), false, null));
} }
// Execute the query and make sure we return distinct results // Execute the query and make sure we return distinct results
List<IBaseResource> retVal = new ArrayList<IBaseResource>(); List<IBaseResource> resources = new ArrayList<IBaseResource>();
loadResourcesByPid(pidsSubList, retVal, revIncludedPids, false); loadResourcesByPid(pidsSubList, resources, revIncludedPids, false);
return retVal; return resources;
} }
}); });
@ -2140,13 +2158,14 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
} }
}; };
ourLog.info("Processed search for {} on {} in {}ms", new Object[] { myResourceName, theParams, w.getMillisAndRestart() }); ourLog.info(" {} on {} in {}ms", new Object[] { myResourceName, theParams, w.getMillisAndRestart() });
return retVal; return retVal;
} }
private List<Long> processSort(final SearchParameterMap theParams, Set<Long> loadPids) { private List<Long> processSort(final SearchParameterMap theParams, Set<Long> theLoadPids) {
final List<Long> pids; final List<Long> pids;
Set<Long> loadPids = theLoadPids;
if (theParams.getSort() != null && isNotBlank(theParams.getSort().getParamName())) { if (theParams.getSort() != null && isNotBlank(theParams.getSort().getParamName())) {
List<Order> orders = new ArrayList<Order>(); List<Order> orders = new ArrayList<Order>();
List<Predicate> predicates = new ArrayList<Predicate>(); List<Predicate> predicates = new ArrayList<Predicate>();
@ -2220,7 +2239,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
for (Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : params.entrySet()) { for (Entry<String, List<List<? extends IQueryParameterType>>> nextParamEntry : params.entrySet()) {
String nextParamName = nextParamEntry.getKey(); String nextParamName = nextParamEntry.getKey();
if (nextParamName.equals("_id")) { if (nextParamName.equals(BaseResource.SP_RES_ID)) {
if (nextParamEntry.getValue().isEmpty()) { if (nextParamEntry.getValue().isEmpty()) {
continue; continue;
@ -2262,7 +2281,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
} }
} }
} else if (nextParamName.equals("_language")) { } else if (nextParamName.equals(BaseResource.SP_RES_LANGUAGE)) {
pids = addPredicateLanguage(pids, nextParamEntry.getValue()); pids = addPredicateLanguage(pids, nextParamEntry.getValue());
@ -2413,10 +2432,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
return qp; return qp;
} }
private IQueryParameterType toParameterType(RuntimeSearchParam theParam, String theValueAsQueryToken) { private IQueryParameterType toParameterType(RuntimeSearchParam theParam, String theQualifier, String theValueAsQueryToken) {
IQueryParameterType qp = toParameterType(theParam); IQueryParameterType qp = toParameterType(theParam);
qp.setValueAsQueryToken(null, theValueAsQueryToken); qp.setValueAsQueryToken(theQualifier, theValueAsQueryToken); // aaaa
return qp; return qp;
} }

View File

@ -37,6 +37,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
@ -55,16 +56,19 @@ import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.validation.DefaultProfileValidationSupport; import ca.uhn.fhir.validation.DefaultProfileValidationSupport;
import ca.uhn.fhir.validation.FhirInstanceValidator; import ca.uhn.fhir.validation.FhirInstanceValidator;
import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.IValidationContext;
import ca.uhn.fhir.validation.IValidationSupport; import ca.uhn.fhir.validation.IValidationSupport;
import ca.uhn.fhir.validation.IValidatorModule;
import ca.uhn.fhir.validation.ValidationResult; import ca.uhn.fhir.validation.ValidationResult;
import ca.uhn.fhir.validation.ValidationSupportChain; import ca.uhn.fhir.validation.ValidationSupportChain;
import net.sourceforge.cobertura.CoverageIgnore;
public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResourceDao<T> { public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResourceDao<T> {
/** /**
* TODO: set this to required after the next release * TODO: set this to required after the next release
*/ */
@Autowired(required=false) @Autowired(required = false)
@Qualifier("myJpaValidationSupportDstu2") @Qualifier("myJpaValidationSupportDstu2")
private IValidationSupport myJpaValidationSupport; private IValidationSupport myJpaValidationSupport;
@ -122,6 +126,8 @@ public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResou
val.setValidationSupport(new ValidationSupportChain(new DefaultProfileValidationSupport(), myJpaValidationSupport)); val.setValidationSupport(new ValidationSupportChain(new DefaultProfileValidationSupport(), myJpaValidationSupport));
validator.registerValidatorModule(val); validator.registerValidatorModule(val);
validator.registerValidatorModule(new IdChecker(theMode));
ValidationResult result; ValidationResult result;
if (isNotBlank(theRawResource)) { if (isNotBlank(theRawResource)) {
result = validator.validateWithResult(theRawResource); result = validator.validateWithResult(theRawResource);
@ -139,4 +145,35 @@ public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResou
} }
private class IdChecker implements IValidatorModule {
private ValidationModeEnum myMode;
public IdChecker(ValidationModeEnum theMode) {
myMode = theMode;
}
@Override
public void validateResource(IValidationContext<IBaseResource> theCtx) {
boolean hasId = theCtx.getResource().getIdElement().hasIdPart();
if (myMode == ValidationModeEnum.CREATE) {
if (hasId) {
throw new InvalidRequestException("Resource has an ID - ID must not be populated for a FHIR create");
}
} else if (myMode == ValidationModeEnum.UPDATE) {
if (hasId == false) {
throw new InvalidRequestException("Resource has no ID - ID must be populated for a FHIR update");
}
}
}
@CoverageIgnore
@Override
public void validateBundle(IValidationContext<Bundle> theContext) {
throw new UnsupportedOperationException();
}
}
} }

View File

@ -166,6 +166,8 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
mySubscriptionFlaggedResourceDataDao.save(flags); mySubscriptionFlaggedResourceDataDao.save(flags);
ourLog.debug("Updating most recent match for subcription {} to {}", subscription.getId().getIdPart(), new InstantDt(mostRecentMatch));
theSubscriptionTable.setMostRecentMatch(mostRecentMatch); theSubscriptionTable.setMostRecentMatch(mostRecentMatch);
myEntityManager.merge(theSubscriptionTable); myEntityManager.merge(theSubscriptionTable);
} }

View File

@ -366,4 +366,83 @@ public class FhirResourceDaoDstu2SubscriptionTest extends BaseJpaDstu2Test {
} }
@Test
public void testSubscriptionResourcesAppear2() throws Exception {
myDaoConfig.setSubscriptionPollDelay(0);
String methodName = "testSubscriptionResourcesAppear2";
Patient p = new Patient();
p.addName().addFamily(methodName);
IIdType pId = myPatientDao.create(p).getId().toUnqualifiedVersionless();
Observation obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
myObservationDao.create(obs).getId().toUnqualifiedVersionless();
Subscription subs;
/*
* Create 2 identical subscriptions
*/
subs = new Subscription();
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
subs.setCriteria("Observation?subject=Patient/" + pId.getIdPart());
subs.setStatus(SubscriptionStatusEnum.ACTIVE);
Long subsId1 = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(mySubscriptionDao.create(subs).getId());
assertNull(mySubscriptionTableDao.findOne(subsId1).getLastClientPoll());
Thread.sleep(100);
ourLog.info("Before: {}", System.currentTimeMillis());
obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
IIdType afterId1 = myObservationDao.create(obs).getId().toUnqualifiedVersionless();
obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
IIdType afterId2 = myObservationDao.create(obs).getId().toUnqualifiedVersionless();
Thread.sleep(100);
ourLog.info("After: {}", System.currentTimeMillis());
List<IBaseResource> results;
List<IIdType> resultIds;
mySubscriptionDao.pollForNewUndeliveredResources();
assertEquals(2, mySubscriptionFlaggedResourceDataDao.count());
Thread.sleep(100);
mySubscriptionDao.pollForNewUndeliveredResources();
assertEquals(2, mySubscriptionFlaggedResourceDataDao.count());
Thread.sleep(100);
mySubscriptionDao.pollForNewUndeliveredResources();
assertEquals(2, mySubscriptionFlaggedResourceDataDao.count());
Thread.sleep(100);
obs = new Observation();
obs.getSubject().setReference(pId);
obs.setStatus(ObservationStatusEnum.FINAL);
myObservationDao.create(obs).getId().toUnqualifiedVersionless();
mySubscriptionDao.pollForNewUndeliveredResources();
assertEquals(3, mySubscriptionFlaggedResourceDataDao.count());
Thread.sleep(100);
mySubscriptionDao.pollForNewUndeliveredResources();
assertEquals(3, mySubscriptionFlaggedResourceDataDao.count());
}
} }

View File

@ -803,6 +803,205 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
assertEquals(id2, gotId); assertEquals(id2, gotId);
} }
@Test
public void testDeleteWithMatchUrlChainedString() {
String methodName = "testDeleteWithMatchUrlChainedString";
Organization org = new Organization();
org.setName(methodName);
IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
Patient p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue(methodName);
p.getManagingOrganization().setReference(orgId);
IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless();
ourLog.info("Created patient, got it: {}", id);
myPatientDao.deleteByUrl("Patient?organization.name=" + methodName);
assertGone(id);
}
@Test
public void testDeleteWithMatchUrlChainedTag() {
String methodName = "testDeleteWithMatchUrlChainedString";
TagList tl = new TagList();
tl.addTag("http://foo", "term");
Organization org = new Organization();
ResourceMetadataKeyEnum.TAG_LIST.put(org, tl);
org.setName(methodName);
IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
Patient p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue(methodName);
p.getManagingOrganization().setReference(orgId);
IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless();
ourLog.info("Created patient, got it: {}", id);
myPatientDao.deleteByUrl("Patient?organization._tag=http://foo|term");
assertGone(id);
myOrganizationDao.deleteByUrl("Organization?_tag=http://foo|term");
try {
myOrganizationDao.read(orgId);
fail();
} catch (ResourceGoneException e) {
// good
}
try {
myPatientDao.deleteByUrl("Patient?organization._tag.identifier=http://foo|term");
fail();
} catch (InvalidRequestException e) {
assertEquals("Invalid parameter chain: organization._tag.identifier", e.getMessage());
}
try {
myOrganizationDao.deleteByUrl("Organization?_tag.identifier=http://foo|term");
fail();
} catch (InvalidRequestException e) {
assertEquals("Invalid parameter chain: _tag.identifier", e.getMessage());
}
}
@Test
public void testDeleteWithMatchUrlQualifierMissing() {
String methodName = "testDeleteWithMatchUrlChainedProfile";
/*
* Org 2 has no name
*/
Organization org1 = new Organization();
org1.addIdentifier().setValue(methodName);
IIdType org1Id = myOrganizationDao.create(org1).getId().toUnqualifiedVersionless();
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue(methodName);
p1.getManagingOrganization().setReference(org1Id);
IIdType patId1 = myPatientDao.create(p1).getId().toUnqualifiedVersionless();
/*
* Org 2 has a name
*/
Organization org2 = new Organization();
org2.setName(methodName);
org2.addIdentifier().setValue(methodName);
IIdType org2Id = myOrganizationDao.create(org2).getId().toUnqualifiedVersionless();
Patient p2 = new Patient();
p2.addIdentifier().setSystem("urn:system").setValue(methodName);
p2.getManagingOrganization().setReference(org2Id);
IIdType patId2 = myPatientDao.create(p2).getId().toUnqualifiedVersionless();
ourLog.info("Pat ID 1 : {}", patId1);
ourLog.info("Org ID 1 : {}", org1Id);
ourLog.info("Pat ID 2 : {}", patId2);
ourLog.info("Org ID 2 : {}", org2Id);
myPatientDao.deleteByUrl("Patient?organization.name:missing=true");
assertGone(patId1);
assertNotGone(patId2);
assertNotGone(org1Id);
assertNotGone(org2Id);
myOrganizationDao.deleteByUrl("Organization?name:missing=true");
assertGone(patId1);
assertNotGone(patId2);
assertGone(org1Id);
assertNotGone(org2Id);
myPatientDao.deleteByUrl("Patient?organization.name:missing=false");
assertGone(patId1);
assertGone(patId2);
assertGone(org1Id);
assertNotGone(org2Id);
myOrganizationDao.deleteByUrl("Organization?name:missing=false");
assertGone(patId1);
assertGone(patId2);
assertGone(org1Id);
assertGone(org2Id);
}
/**
* This gets called from assertGone too! Careful about exceptions...
*/
private void assertNotGone(IIdType theId) {
if ("Patient".equals(theId.getResourceType())) {
myPatientDao.read(theId);
} else if ("Organization".equals(theId.getResourceType())){
myOrganizationDao.read(theId);
} else {
fail("No type");
}
}
private void assertGone(IIdType theId) {
try {
assertNotGone(theId);
fail();
} catch (ResourceGoneException e) {
// good
}
}
@Test
public void testDeleteWithMatchUrlChainedProfile() {
String methodName = "testDeleteWithMatchUrlChainedProfile";
List<IdDt> profileList = new ArrayList<IdDt>();
profileList.add(new IdDt("http://foo"));
Organization org = new Organization();
ResourceMetadataKeyEnum.PROFILES.put(org, profileList);
org.setName(methodName);
IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
Patient p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue(methodName);
p.getManagingOrganization().setReference(orgId);
IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless();
ourLog.info("Created patient, got it: {}", id);
myPatientDao.deleteByUrl("Patient?organization._profile=http://foo");
assertGone(id);
myOrganizationDao.deleteByUrl("Organization?_profile=http://foo");
try {
myOrganizationDao.read(orgId);
fail();
} catch (ResourceGoneException e) {
// good
}
try {
myPatientDao.deleteByUrl("Patient?organization._profile.identifier=http://foo");
fail();
} catch (InvalidRequestException e) {
assertEquals("Invalid parameter chain: organization._profile.identifier", e.getMessage());
}
try {
myOrganizationDao.deleteByUrl("Organization?_profile.identifier=http://foo");
fail();
} catch (InvalidRequestException e) {
assertEquals("Invalid parameter chain: _profile.identifier", e.getMessage());
}
}
@Test @Test
public void testDeleteWithMatchUrl() { public void testDeleteWithMatchUrl() {
String methodName = "testDeleteWithMatchUrl"; String methodName = "testDeleteWithMatchUrl";
@ -840,8 +1039,6 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
} }
@Test @Test
public void testHistoryByForcedId() { public void testHistoryByForcedId() {
IIdType idv1; IIdType idv1;
@ -1619,6 +1816,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
} }
} }
@Test @Test
public void testReadWithDeletedResource() { public void testReadWithDeletedResource() {
String methodName = "testReadWithDeletedResource"; String methodName = "testReadWithDeletedResource";
@ -1628,12 +1826,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
IIdType id = myPatientDao.create(patient).getId().toVersionless(); IIdType id = myPatientDao.create(patient).getId().toVersionless();
myPatientDao.delete(id); myPatientDao.delete(id);
try { assertGone(id);
myPatientDao.read(id);
fail();
} catch (ResourceGoneException e) {
// good
}
patient.setId(id); patient.setId(id);
patient.addAddress().addLine("AAA"); patient.addAddress().addLine("AAA");

View File

@ -24,6 +24,7 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.ValidationModeEnum; import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.EncodingEnum;
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.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
@ -127,6 +128,70 @@ public class FhirResourceDaoDstu2ValidateTest extends BaseJpaDstu2Test {
} }
@Test
public void testValidateForCreate() {
String methodName = "testValidateForCreate";
Patient pat = new Patient();
pat.setId("Patient/123");
pat.addName().addFamily(methodName);
try {
myPatientDao.validate(pat, null, null, null, ValidationModeEnum.CREATE, null);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("ID must not be populated"));
}
pat.setId("");
myPatientDao.validate(pat, null, null, null, ValidationModeEnum.CREATE, null);
}
@Test
public void testValidateForUpdate() {
String methodName = "testValidateForUpdate";
Patient pat = new Patient();
pat.setId("Patient/123");
pat.addName().addFamily(methodName);
myPatientDao.validate(pat, null, null, null, ValidationModeEnum.UPDATE, null);
pat.setId("");
try {
myPatientDao.validate(pat, null, null, null, ValidationModeEnum.UPDATE, null);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("ID must be populated"));
}
}
@Test
public void testValidateForUpdateWithContained() {
String methodName = "testValidateForUpdate";
Organization org = new Organization();
org.setId("#123");
Patient pat = new Patient();
pat.setId("Patient/123");
pat.addName().addFamily(methodName);
myPatientDao.validate(pat, null, null, null, ValidationModeEnum.UPDATE, null);
pat.setId("");
try {
myPatientDao.validate(pat, null, null, null, ValidationModeEnum.UPDATE, null);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("ID must be populated"));
}
}
@Test @Test
public void testValidateForDelete() { public void testValidateForDelete() {
String methodName = "testValidateForDelete"; String methodName = "testValidateForDelete";

View File

@ -60,9 +60,7 @@ import ca.uhn.fhir.model.dstu2.valueset.IssueSeverityEnum;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.api.PreferReturnEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -75,6 +73,13 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoDstu2Test.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoDstu2Test.class);
@Test
public void testTransactionFromBundle6() throws Exception {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/simone_bundle3.xml");
String bundle = IOUtils.toString(bundleRes);
mySystemDao.transaction(myFhirCtx.newXmlParser().parseResource(Bundle.class, bundle));
}
@Test @Test
public void testRendexing() { public void testRendexing() {
Patient p = new Patient(); Patient p = new Patient();
@ -292,6 +297,23 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
} }
@Test
public void testEverythingType() {
Bundle request = new Bundle();
request.setType(BundleTypeEnum.SEARCH_RESULTS);
Patient p = new Patient();
request.addEntry().setResource(p).getRequest().setMethod(HTTPVerbEnum.POST);
try {
mySystemDao.transaction(request);
fail();
} catch (InvalidRequestException e) {
assertEquals("Unable to process transaction where incoming Bundle.type = searchset", e.getMessage());
}
}
@Test @Test
public void testTransactionBatchWithFailingRead() { public void testTransactionBatchWithFailingRead() {
String methodName = "testTransactionBatchWithFailingRead"; String methodName = "testTransactionBatchWithFailingRead";

View File

@ -0,0 +1,13 @@
package ca.uhn.fhir.jpa.provider;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.resource.Questionnaire;
public class QuestionnaireResourceProviderDstu2 extends JpaResourceProviderDstu1<Questionnaire> {
@Override
public Class<? extends IResource> getResourceType() {
return Questionnaire.class;
}
}

View File

@ -2006,14 +2006,15 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
String inputStr = myFhirCtx.newXmlParser().encodeResourceToString(input); String inputStr = myFhirCtx.newXmlParser().encodeResourceToString(input);
ourLog.info(inputStr); ourLog.info(inputStr);
HttpPost post = new HttpPost(ourServerBase + "/Patient/$validate"); HttpPost post = new HttpPost(ourServerBase + "/Patient/$validate?_pretty=true");
post.setEntity(new StringEntity(inputStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); post.setEntity(new StringEntity(inputStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post); CloseableHttpResponse response = ourHttpClient.execute(post);
try { try {
String resp = IOUtils.toString(response.getEntity().getContent()); String resp = IOUtils.toString(response.getEntity().getContent());
ourLog.info(resp); ourLog.info(resp);
assertEquals(412, response.getStatusLine().getStatusCode()); assertEquals(200, response.getStatusLine().getStatusCode());
assertThat(resp, not(containsString("Resource has no id")));
} finally { } finally {
IOUtils.closeQuietly(response.getEntity().getContent()); IOUtils.closeQuietly(response.getEntity().getContent());
response.close(); response.close();

View File

@ -3,8 +3,14 @@ package ca.uhn.fhir.jpa.provider;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.io.InputStream; import java.io.InputStream;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
@ -17,14 +23,14 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.BaseJpaTest; import ca.uhn.fhir.jpa.dao.BaseJpaTest;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.rp.dstu.ObservationResourceProvider; import ca.uhn.fhir.jpa.rp.dstu2.ObservationResourceProvider;
import ca.uhn.fhir.jpa.rp.dstu.OrganizationResourceProvider; import ca.uhn.fhir.jpa.rp.dstu2.OrganizationResourceProvider;
import ca.uhn.fhir.jpa.rp.dstu.PatientResourceProvider; import ca.uhn.fhir.jpa.rp.dstu2.PatientResourceProvider;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.model.dstu.resource.Observation; import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu.resource.Organization; import ca.uhn.fhir.model.dstu2.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu.resource.Questionnaire; import ca.uhn.fhir.model.dstu2.resource.Questionnaire;
import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.OperationDefinition; import ca.uhn.fhir.model.dstu2.resource.OperationDefinition;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome; import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
@ -40,7 +46,19 @@ public class SystemProviderDstu2Test extends BaseJpaTest {
private static ClassPathXmlApplicationContext ourAppCtx; private static ClassPathXmlApplicationContext ourAppCtx;
private static FhirContext ourCtx; private static FhirContext ourCtx;
private static IGenericClient ourClient; private static IGenericClient ourClient;
private static String ourServerBase;
private static CloseableHttpClient ourHttpClient;
@Test
public void testEverythingType() throws Exception {
HttpGet get = new HttpGet(ourServerBase + "/Patient/$everything");
CloseableHttpResponse http = ourHttpClient.execute(get);
try {
assertEquals(200, http.getStatusLine().getStatusCode());
} finally {
http.close();
}
}
@Test @Test
public void testTransactionFromBundle4() throws Exception { public void testTransactionFromBundle4() throws Exception {
@ -71,6 +89,20 @@ public class SystemProviderDstu2Test extends BaseJpaTest {
} }
} }
@Test
public void testTransactionFromBundle6() throws Exception {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/simone_bundle3.xml");
String bundle = IOUtils.toString(bundleRes);
ourClient.transaction().withBundle(bundle).prettyPrint().execute();
// try {
// fail();
// } catch (InvalidRequestException e) {
// OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
// assertEquals("Invalid placeholder ID found: uri:uuid:bb0cd4bc-1839-4606-8c46-ba3069e69b1d - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'", oo.getIssue().get(0).getDiagnostics());
// assertEquals("processing", oo.getIssue().get(0).getCode());
// }
}
@Test @Test
public void testTransactionFromBundle() throws Exception { public void testTransactionFromBundle() throws Exception {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/transaction_link_patient_eve.xml"); InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/transaction_link_patient_eve.xml");
@ -82,7 +114,7 @@ public class SystemProviderDstu2Test extends BaseJpaTest {
/** /**
* This is Gramahe's test transaction - it requires some set up in order to work * This is Gramahe's test transaction - it requires some set up in order to work
*/ */
// @Test // @Test
public void testTransactionFromBundle3() throws Exception { public void testTransactionFromBundle3() throws Exception {
InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/grahame-transaction.xml"); InputStream bundleRes = SystemProviderDstu2Test.class.getResourceAsStream("/grahame-transaction.xml");
@ -150,7 +182,7 @@ public class SystemProviderDstu2Test extends BaseJpaTest {
patientRp.setDao(patientDao); patientRp.setDao(patientDao);
IFhirResourceDao<Questionnaire> questionnaireDao = (IFhirResourceDao<Questionnaire>) ourAppCtx.getBean("myQuestionnaireDaoDstu2", IFhirResourceDao.class); IFhirResourceDao<Questionnaire> questionnaireDao = (IFhirResourceDao<Questionnaire>) ourAppCtx.getBean("myQuestionnaireDaoDstu2", IFhirResourceDao.class);
QuestionnaireResourceProvider questionnaireRp = new QuestionnaireResourceProvider(); QuestionnaireResourceProviderDstu2 questionnaireRp = new QuestionnaireResourceProviderDstu2();
questionnaireRp.setDao(questionnaireDao); questionnaireRp.setDao(questionnaireDao);
IFhirResourceDao<Observation> observationDao = (IFhirResourceDao<Observation>) ourAppCtx.getBean("myObservationDaoDstu2", IFhirResourceDao.class); IFhirResourceDao<Observation> observationDao = (IFhirResourceDao<Observation>) ourAppCtx.getBean("myObservationDaoDstu2", IFhirResourceDao.class);
@ -173,7 +205,7 @@ public class SystemProviderDstu2Test extends BaseJpaTest {
ServletContextHandler proxyHandler = new ServletContextHandler(); ServletContextHandler proxyHandler = new ServletContextHandler();
proxyHandler.setContextPath("/"); proxyHandler.setContextPath("/");
String serverBase = "http://localhost:" + myPort + "/fhir/context"; ourServerBase = "http://localhost:" + myPort + "/fhir/context";
ServletHolder servletHolder = new ServletHolder(); ServletHolder servletHolder = new ServletHolder();
servletHolder.setServlet(restServer); servletHolder.setServlet(restServer);
@ -185,9 +217,13 @@ public class SystemProviderDstu2Test extends BaseJpaTest {
ourServer.setHandler(proxyHandler); ourServer.setHandler(proxyHandler);
ourServer.start(); ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourHttpClient = builder.build();
ourCtx.getRestfulClientFactory().setSocketTimeout(600 * 1000); ourCtx.getRestfulClientFactory().setSocketTimeout(600 * 1000);
ourClient = ourCtx.newRestfulGenericClient(serverBase); ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
ourClient.setLogRequestAndResponse(true); ourClient.setLogRequestAndResponse(true);
} }

View File

@ -1,13 +1,20 @@
<configuration> <configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<encoder> <encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern> </pattern>
</encoder> </encoder>
</appender> </appender>
<logger name="org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator" additivity="false" info="debug"> <logger name="org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator" additivity="false" level="debug">
<appender-ref ref="STDOUT" />
</logger>
<logger name="ca.uhn.fhir.jpa.dao.FhirResourceDaoSubscriptionDstu2" additivity="false" level="debug">
<appender-ref ref="STDOUT" /> <appender-ref ref="STDOUT" />
</logger> </logger>

View File

@ -0,0 +1,241 @@
<Bundle xmlns="http://hl7.org/fhir">
<id value="20151004163829"/>
<type value="transaction"/>
<entry>
<fullUrl value="urn:uuid:87c8e948-e0d2-4b92-74bd-a824a4769ab4"/>
<resource>
<Patient>
<identifier>
<use value="usual"/>
<type>
<coding>
<system value="http://hl7.org/fhir/identifier-type"/>
<code value="NH"/>
</coding>
</type>
<system value="urn:oid:2.16.840.1.113883.2.1.4.1"/>
<value value="123412312345"/>
<assigner>
<display value="NHS"/>
</assigner>
</identifier>
<identifier>
<use value="usual"/>
<type>
<coding>
<system value="http://hl7.org/fhir/identifier-type"/>
<code value="MR"/>
</coding>
</type>
<system value="http://www.ghh.org/identifiers"/>
<value value="456756756745"/>
<assigner>
<display value="TCPAS"/>
</assigner>
</identifier>
<name>
<use value="official"/>
<family value="Connectathon"/>
<given value="Ten"/>
</name>
<telecom>
<system value="phone"/>
<value value="277543"/>
<use value="home"/>
</telecom>
<gender value="female"/>
<birthDate value="2015-05-24"/>
<address>
<line value="22 Peachtree Road"/>
<city value="Whitstable"/>
<state value="Kent"/>
<country value="CR5 1EL"/>
</address>
<contact>
<relationship>
<coding>
<system value="http://hl7.org/fhir/patient-contact-relationship"/>
<code value="parent"/>
</coding>
</relationship>
<name>
<family value="Connectathon"/>
<given value="Nine"/>
</name>
</contact>
<contact>
<relationship>
<coding>
<system value="http://hl7.org/fhir/patient-contact-relationship"/>
</coding>
</relationship>
<name>
<family value="Connectathon"/>
<given value="Eight"/>
</name>
</contact>
</Patient>
</resource>
<request>
<method value="PUT"/>
<url value="Patient?identifier=http://www.ghh.org/identifiers|456756756745"/>
</request>
</entry>
<entry>
<fullUrl value="urn:uuid:b23ee048-63c1-4373-6194-54adb2f64821"/>
<resource>
<Encounter>
<identifier>
<use value="usual"/>
<type>
<coding>
<system value="http://hl7.org/fhir/identifier-type"/>
<code value="MR"/>
</coding>
</type>
<system value="http://general-hospital.co.uk/Identifiers"/>
<value value="09876876876"/>
<assigner>
<display value="GENHOS"/>
</assigner>
</identifier>
<status value="in-progress"/>
<class value="inpatient"/>
<patient>
<reference value="urn:uuid:87c8e948-e0d2-4b92-74bd-a824a4769ab4"/>
<display value="Connectathon, Ten(*24.05.2015)"/>
</patient>
<period>
<start value="2015-05-02T09:00:00+01:00"/>
</period>
</Encounter>
</resource>
<request>
<method value="PUT"/>
<url value="Encounter?identifier=http://general-hospital.co.uk/Identifiers|09876876876"/>
</request>
</entry>
<entry>
<request>
<method value="DELETE"/>
<url value="AllergyIntolerance?patient.identifier=http://general-hospital.co.uk/Identifiers|123412312345"/>
</request>
</entry>
<entry>
<resource>
<AllergyIntolerance>
<identifier>
<system value="http://health-comm.de/identifiers"/>
<value value="123412312345-DA-1605"/>
<assigner>
<display value="CLOVERLEAF"/>
</assigner>
</identifier>
<patient>
<reference value="urn:uuid:87c8e948-e0d2-4b92-74bd-a824a4769ab4"/>
<display value="Connectathon, Ten(*24.05.2015)"/>
</patient>
<substance>
<coding>
<code value="1605"/>
<display value="L"/>
</coding>
<text value="acetaminophen"/>
</substance>
<category value="medication"/>
<reaction>
<manifestation>
<text value="Muscle Pain"/>
</manifestation>
<severity value="moderate"/>
</reaction>
<reaction>
<manifestation>
<text value="hair loss"/>
</manifestation>
<severity value="moderate"/>
</reaction>
</AllergyIntolerance>
</resource>
<request>
<method value="PUT"/>
<url value="AllergyIntolerance?identifier=http://health-comm.de/identifiers|123412312345-DA-1605"/>
</request>
</entry>
<entry>
<resource>
<AllergyIntolerance>
<identifier>
<system value="http://health-comm.de/identifiers"/>
<value value="123412312345-DA-1558"/>
<assigner>
<display value="CLOVERLEAF"/>
</assigner>
</identifier>
<patient>
<reference value="urn:uuid:87c8e948-e0d2-4b92-74bd-a824a4769ab4"/>
<display value="Connectathon, Ten(*24.05.2015)"/>
</patient>
<substance>
<coding>
<code value="1558"/>
<display value="L"/>
</coding>
<text value="Oxycodone"/>
</substance>
<category value="medication"/>
<reaction>
<manifestation>
<text value="Muscle Pain"/>
</manifestation>
<severity value="moderate"/>
</reaction>
<reaction>
<manifestation>
<text value="hair loss"/>
</manifestation>
<severity value="moderate"/>
</reaction>
</AllergyIntolerance>
</resource>
<request>
<method value="PUT"/>
<url value="AllergyIntolerance?identifier=http://health-comm.de/identifiers|123412312345-DA-1558"/>
</request>
</entry>
<entry>
<resource>
<AllergyIntolerance>
<identifier>
<system value="http://health-comm.de/identifiers"/>
<value value="123412312345-MA-2221"/>
<assigner>
<display value="CLOVERLEAF"/>
</assigner>
</identifier>
<patient>
<reference value="urn:uuid:87c8e948-e0d2-4b92-74bd-a824a4769ab4"/>
<display value="Connectathon, Ten(*24.05.2015)"/>
</patient>
<substance>
<coding>
<code value="2221"/>
<display value="L"/>
</coding>
<text value="Peanuts"/>
</substance>
<category value="environment"/>
<reaction>
<manifestation>
<text value="Anaphylactic Shock "/>
</manifestation>
<severity value="severe"/>
</reaction>
</AllergyIntolerance>
</resource>
<request>
<method value="PUT"/>
<url value="AllergyIntolerance?identifier=http://health-comm.de/identifiers|123412312345-MA-2221"/>
</request>
</entry>
</Bundle>

View File

@ -169,8 +169,6 @@
<configuration> <configuration>
<webApp> <webApp>
<contextPath>/hapi-fhir-jpaserver-example</contextPath> <contextPath>/hapi-fhir-jpaserver-example</contextPath>
</webApp>
<webAppConfig>
<allowDuplicateFragmentNames>true</allowDuplicateFragmentNames> <allowDuplicateFragmentNames>true</allowDuplicateFragmentNames>
</webAppConfig> </webAppConfig>
</configuration> </configuration>

View File

@ -1026,6 +1026,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return parts.length > 2 && parts[parts.length - 1].equals("resource") && ((parts.length > 2 && parts[parts.length - 3].equals("entry")) || parts[parts.length - 2].equals("entry")); return parts.length > 2 && parts[parts.length - 1].equals("resource") && ((parts.length > 2 && parts[parts.length - 3].equals("entry")) || parts[parts.length - 2].equals("entry"));
} }
private boolean isParametersEntry(String path) {
String[] parts = path.split("\\/");
if (path.startsWith("/f:"))
return parts.length == 4 && parts[parts.length-3].equals("f:Parameters") && parts[parts.length-2].startsWith("f:parameter") && parts[parts.length-1].startsWith("f:resource");
else
return parts.length == 4 && parts[parts.length-3].equals("Parameters") && parts[parts.length-2].startsWith("parameter") && parts[parts.length-1].startsWith("resource");
}
private boolean isPrimitiveType(String type) { private boolean isPrimitiveType(String type) {
return type.equalsIgnoreCase("boolean") || type.equalsIgnoreCase("integer") || type.equalsIgnoreCase("string") || type.equalsIgnoreCase("decimal") || type.equalsIgnoreCase("uri") return type.equalsIgnoreCase("boolean") || type.equalsIgnoreCase("integer") || type.equalsIgnoreCase("string") || type.equalsIgnoreCase("decimal") || type.equalsIgnoreCase("uri")
|| type.equalsIgnoreCase("base64Binary") || type.equalsIgnoreCase("instant") || type.equalsIgnoreCase("date") || type.equalsIgnoreCase("uuid") || type.equalsIgnoreCase("id") || type.equalsIgnoreCase("base64Binary") || type.equalsIgnoreCase("instant") || type.equalsIgnoreCase("date") || type.equalsIgnoreCase("uuid") || type.equalsIgnoreCase("id")
@ -1790,7 +1798,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (type.equals("Extension")) { if (type.equals("Extension")) {
checkExtension(errors, ei.path, ei.element, ei.definition, profile, localStack); checkExtension(errors, ei.path, ei.element, ei.definition, profile, localStack);
} else if (type.equals("Resource")) { } else if (type.equals("Resource")) {
validateContains(errors, ei.path, ei.definition, definition, ei.element, localStack, !isBundleEntry(ei.path)); // if validateContains(errors, ei.path, ei.definition, definition, ei.element, localStack, !isBundleEntry(ei.path) && !isParametersEntry(ei.path)); // if
// (str.matches(".*([.,/])work\\1$")) // (str.matches(".*([.,/])work\\1$"))
} else { } else {
StructureDefinition p = getProfileForType(type); StructureDefinition p = getProfileForType(type);

View File

@ -139,6 +139,13 @@
of the narrative section caused an invalid of the narrative section caused an invalid
re-encoding when encoding to JSON. re-encoding when encoding to JSON.
</action> </action>
<action type="fix">
Conditional deletes in JPA did not correctly
process if the condition had a chain or a
qualifier, e.g. "Patient?organization.name" or
"Patient.identifier:missing"
</action>
</release> </release>
<release version="1.2" date="2015-09-18"> <release version="1.2" date="2015-09-18">
<action type="add"> <action type="add">