Fix #534: Add setting for logical references to DAOConfig

Merge branch 'master' into issue534
This commit is contained in:
James Agnew 2017-03-30 03:21:50 +08:00
commit 2c9a6e65e7
18 changed files with 591 additions and 319 deletions

View File

@ -45,7 +45,7 @@ public interface ISort<T> {
/**
* Sort descending
*
* @param A query param - Could be a constant such as <code>Patient.ADDRESS</code> or a custom
* @param theParam A query param - Could be a constant such as <code>Patient.ADDRESS</code> or a custom
* param such as <code>new StringClientParam("foo")</code>
*/
IQuery<T> descending(IParam theParam);

View File

@ -187,12 +187,6 @@ public interface IServerInterceptor {
* A bean containing details about the request that is about to be processed, including
* @param theResponseObject
* The actual object which is being streamed to the client as a response
* @param theServletRequest
* The incoming request
* @param theServletResponse
* The response. Note that interceptors may choose to provide a response (i.e. by calling
* {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
* to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
@ -201,7 +195,7 @@ public interface IServerInterceptor {
* This exception may be thrown to indicate that the interceptor has detected an unauthorized access
* attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
*/
boolean outgoingResponse(RequestDetails theRequest, Bundle bundle);
boolean outgoingResponse(RequestDetails theRequest, Bundle theResponseObject);
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the

View File

@ -20,6 +20,16 @@ package ca.uhn.fhir.jpa.config;
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.validation.IValidatorModule;
import org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.instance.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.instance.validation.IResourceValidator.BestPracticeWarningLevel;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -27,17 +37,6 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
import ca.uhn.fhir.jpa.dao.SearchParamExtractorDstu2;
import ca.uhn.fhir.jpa.dao.SearchParamRegistryDstu2;
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
@Configuration
@EnableTransactionManagement
public class BaseDstu2Config extends BaseConfig {
@ -75,6 +74,15 @@ public class BaseDstu2Config extends BaseConfig {
return retVal;
}
@Bean(name = "myInstanceValidatorDstu2")
@Lazy
public IValidatorModule instanceValidatorDstu2() {
FhirInstanceValidator retVal = new FhirInstanceValidator();
retVal.setBestPracticeWarningLevel(BestPracticeWarningLevel.Warning);
retVal.setValidationSupport(new ValidationSupportChain(new DefaultProfileValidationSupport(), jpaValidationSupportDstu2()));
return retVal;
}
@Bean(autowire = Autowire.BY_TYPE)
public IFulltextSearchSvc searchDao() {
FulltextSearchSvcImpl searchDao = new FulltextSearchSvcImpl();

View File

@ -312,29 +312,14 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
}
}
Set<String> treatReferencesAsLogical = myConfig.getTreatReferencesAsLogical();
if (treatReferencesAsLogical != null) {
boolean isLogical = false;
for (String nextLogicalRef : treatReferencesAsLogical) {
nextLogicalRef = trim(nextLogicalRef);
if (nextLogicalRef.charAt(nextLogicalRef.length() - 1) == '*') {
if (nextId.getValue().startsWith(nextLogicalRef.substring(0, nextLogicalRef.length() -1))) {
isLogical = true;
break;
}
} else {
if (nextId.getValue().equals(nextLogicalRef)) {
isLogical = true;
break;
}
}
}
if (isLogical) {
continue;
if (isLogicalReference(nextId)) {
ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime);
if (theLinks.add(resourceLink)) {
ourLog.info("Indexing remote resource reference URL: {}", nextId);
}
continue;
}
String baseUrl = nextId.getBaseUrl();
String typeString = nextId.getResourceType();
if (isBlank(typeString)) {
@ -412,6 +397,26 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
}
protected boolean isLogicalReference(IIdType theId) {
Set<String> treatReferencesAsLogical = myConfig.getTreatReferencesAsLogical();
if (treatReferencesAsLogical != null) {
for (String nextLogicalRef : treatReferencesAsLogical) {
nextLogicalRef = trim(nextLogicalRef);
if (nextLogicalRef.charAt(nextLogicalRef.length() - 1) == '*') {
if (theId.getValue().startsWith(nextLogicalRef.substring(0, nextLogicalRef.length() - 1))) {
return true;
}
} else {
if (theId.getValue().equals(nextLogicalRef)) {
return true;
}
}
}
}
return false;
}
protected Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
return mySearchParamExtractor.extractSearchParamCoords(theEntity, theResource);
}
@ -959,12 +964,11 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* Subclasses may override to provide behaviour. Called when a resource has been inserted into the database for the first time.
*
* @param theEntity
* The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
* The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
* @param theTag
* The tag
* The tag
* @return Returns <code>true</code> if the tag should be removed
*/
@SuppressWarnings("unused")
protected void postPersist(ResourceTable theEntity, T theResource) {
// nothing
}
@ -973,9 +977,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database
*
* @param theEntity
* The resource
* The resource
* @param theResource
* The resource being persisted
* The resource being persisted
*/
protected void postUpdate(ResourceTable theEntity, T theResource) {
// nothing
@ -998,7 +1002,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return ids;
}
@SuppressWarnings("unused")
@CoverageIgnore
public BaseHasResource readEntity(IIdType theValueId) {
throw new NotImplementedException("");
@ -1031,9 +1034,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* </p>
*
* @param theEntity
* The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
* The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
* @param theTag
* The tag
* The tag
* @return Retturns <code>true</code> if the tag should be removed
*/
protected boolean shouldDroppedTagBeRemovedOnUpdate(ResourceTable theEntity, ResourceTag theTag) {
@ -1269,7 +1272,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
setUpdatedTime(uriParams, theUpdateTime);
setUpdatedTime(coordsParams, theUpdateTime);
setUpdatedTime(tokenParams, theUpdateTime);
/*
* Handle references within the resource that are match URLs, for example references like "Patient?identifier=foo". These match URLs are resolved and replaced with the ID of the
* matching
@ -1531,11 +1534,14 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
for (IBase nextChild : values) {
IBaseReference nextRef = (IBaseReference) nextChild;
if (!isBlank(nextRef.getReferenceElement().getResourceType())) {
if (!nextRef.getReferenceElement().getValue().contains("?")) {
if (!validTypes.contains(nextRef.getReferenceElement().getResourceType())) {
throw new UnprocessableEntityException(
"Invalid reference found at path '" + newPath + "'. Resource type '" + nextRef.getReferenceElement().getResourceType() + "' is not valid for this path");
IIdType referencedId = nextRef.getReferenceElement();
if (!isBlank(referencedId.getResourceType())) {
if (!isLogicalReference(referencedId)) {
if (!referencedId.getValue().contains("?")) {
if (!validTypes.contains(referencedId.getResourceType())) {
throw new UnprocessableEntityException(
"Invalid reference found at path '" + newPath + "'. Resource type '" + referencedId.getResourceType() + "' is not valid for this path");
}
}
}
}
@ -1575,9 +1581,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* "subsetted" tag and rejects resources which have it. Subclasses should call the superclass implementation to preserve this check.
*
* @param theResource
* The resource that is about to be persisted
* The resource that is about to be persisted
* @param theEntityToSave
* TODO
* TODO
*/
protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) {
Object tag = null;

View File

@ -56,7 +56,8 @@ public class DaoConfig {
// ***
// update setter javadoc if default changes
// ***
private boolean myAllowInlineMatchUrlReferences = false;
private boolean myAllowInlineMatchUrlReferences = true;
private boolean myAllowMultipleDelete;
private boolean myDefaultSearchParamsCanBeOverridden = false;
// ***
@ -93,6 +94,20 @@ public class DaoConfig {
private Set<String> myTreatBaseUrlsAsLocal = new HashSet<String>();
private Set<String> myTreatReferencesAsLogical = new HashSet<String>(DEFAULT_LOGICAL_BASE_URLS);
/**
* Add a value to the {@link #setTreatReferencesAsLogical(Set) logical references list}.
*
* @see #setTreatReferencesAsLogical(Set)
*/
public void addTreatReferencesAsLogical(String theTreatReferencesAsLogical) {
validateTreatBaseUrlsAsLocal(theTreatReferencesAsLogical);
if (myTreatReferencesAsLogical == null) {
myTreatReferencesAsLogical = new HashSet<String>();
}
myTreatReferencesAsLogical.add(theTreatReferencesAsLogical);
}
/**
* When a code system is added that contains more than this number of codes,
* the code system will be indexed later in an incremental process in order to
@ -115,9 +130,9 @@ public class DaoConfig {
* (next/prev links in search response bundles) will become invalid. Defaults to 1 hour.
* </p>
* <p>
*
* @see To disable this feature entirely, see {@link #setExpireSearchResults(boolean)}
* </p>
* <p>
* To disable this feature entirely, see {@link #setExpireSearchResults(boolean)}
* </p>
*
* @since 1.5
*/
@ -196,9 +211,9 @@ public class DaoConfig {
* references instead of being treated as real references.
* <p>
* A logical reference is a reference which is treated as an identifier, and
* does not neccesarily resolve. See {@link http://hl7.org/fhir/references.html} for
* does not neccesarily resolve. See {@link "http://hl7.org/fhir/references.html"} for
* a description of logical references. For example, the valueset
* {@link http://hl7.org/fhir/valueset-quantity-comparator.html} is a logical
* {@link "http://hl7.org/fhir/valueset-quantity-comparator.html"} is a logical
* reference.
* </p>
* <p>
@ -209,7 +224,7 @@ public class DaoConfig {
* <li><code>http://example.com/some-base*</code> <b>(will match anything beginning with the part before the *)</b></li>
* </ul>
*
* @see #DEFAULT_LOGICAL_BASE_URLS for a list of default values for this setting
* @see #DEFAULT_LOGICAL_BASE_URLS Default values for this property
*/
public Set<String> getTreatReferencesAsLogical() {
return myTreatReferencesAsLogical;
@ -337,7 +352,9 @@ public class DaoConfig {
* to "Patient?identifier=12345", this is reference match URL will be resolved and replaced according
* to the usual match URL rules.
* <p>
* Default is false for now, as this is an experimental feature.
* Default is {@literal true} beginning in HAPI FHIR 2.4, since this
* feature is now specified in the FHIR specification. (Previously it
* was an experimental/rpposed feature)
* </p>
*
* @since 1.5
@ -401,8 +418,10 @@ public class DaoConfig {
* </p>
* <p>
*
* @see To disable this feature entirely, see {@link #setExpireSearchResults(boolean)}
* </p>
* <p>
* To disable this feature entirely, see {@link #setExpireSearchResults(boolean)}
* </p>
*
* @since 1.5
*/
public void setExpireSearchResultsAfterMillis(long theExpireSearchResultsAfterMillis) {
@ -418,7 +437,7 @@ public class DaoConfig {
* paging provider instead. Deprecated in HAPI FHIR 2.3 (Jan 2017)
*/
@Deprecated
public void setHardSearchLimit(@SuppressWarnings("unused") int theHardSearchLimit) {
public void setHardSearchLimit(int theHardSearchLimit) {
// this method does nothing
}
@ -528,6 +547,12 @@ public class DaoConfig {
* means no references will be treated as external
*/
public void setTreatBaseUrlsAsLocal(Set<String> theTreatBaseUrlsAsLocal) {
if (theTreatBaseUrlsAsLocal != null) {
for (String next : theTreatBaseUrlsAsLocal) {
validateTreatBaseUrlsAsLocal(next);
}
}
HashSet<String> treatBaseUrlsAsLocal = new HashSet<String>();
for (String next : ObjectUtils.defaultIfNull(theTreatBaseUrlsAsLocal, new HashSet<String>())) {
while (next.endsWith("/")) {
@ -544,9 +569,9 @@ public class DaoConfig {
* references instead of being treated as real references.
* <p>
* A logical reference is a reference which is treated as an identifier, and
* does not neccesarily resolve. See {@link http://hl7.org/fhir/references.html} for
* does not neccesarily resolve. See {@link "http://hl7.org/fhir/references.html"} for
* a description of logical references. For example, the valueset
* {@link http://hl7.org/fhir/valueset-quantity-comparator.html} is a logical
* {@link "http://hl7.org/fhir/valueset-quantity-comparator.html"} is a logical
* reference.
* </p>
* <p>
@ -557,11 +582,23 @@ public class DaoConfig {
* <li><code>http://example.com/some-base*</code> <b>(will match anything beginning with the part before the *)</b></li>
* </ul>
*
* @see #DEFAULT_LOGICAL_BASE_URLS for a list of default values for this setting
* @see #DEFAULT_LOGICAL_BASE_URLS Default values for this property
*/
public DaoConfig setTreatReferencesAsLogical(Set<String> theTreatReferencesAsLogical) {
myTreatReferencesAsLogical = theTreatReferencesAsLogical;
return this;
}
private static void validateTreatBaseUrlsAsLocal(String theUrl) {
Validate.notBlank(theUrl, "Base URL must not be null or empty");
int starIdx = theUrl.indexOf('*');
if (starIdx != -1) {
if (starIdx != theUrl.length() - 1) {
throw new IllegalArgumentException("Base URL wildcard character (*) can only appear at the end of the string: " + theUrl);
}
}
}
}

View File

@ -1,43 +1,5 @@
package ca.uhn.fhir.jpa.dao;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.ArrayList;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.util.Collections;
import java.util.List;
import org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.instance.hapi.validation.IValidationSupport;
import org.hl7.fhir.instance.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.validation.IResourceValidator.BestPracticeWarningLevel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.entity.ResourceTable;
@ -64,6 +26,38 @@ import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.IValidationContext;
import ca.uhn.fhir.validation.IValidatorModule;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.instance.hapi.validation.IValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResourceDao<T> {
@ -71,6 +65,9 @@ public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResou
@Qualifier("myJpaValidationSupportDstu2")
private IValidationSupport myJpaValidationSupport;
@Autowired()
@Qualifier("myInstanceValidatorDstu2")
private IValidatorModule myInstanceValidator;
@Override
protected List<Object> getIncludeValues(FhirTerser theTerser, Include theInclude, IBaseResource theResource, RuntimeResourceDefinition theResourceDef) {
@ -124,10 +121,7 @@ public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResou
FhirValidator validator = getContext().newValidator();
FhirInstanceValidator val = new FhirInstanceValidator();
val.setBestPracticeWarningLevel(BestPracticeWarningLevel.Warning);
val.setValidationSupport(new ValidationSupportChain(new DefaultProfileValidationSupport(), myJpaValidationSupport));
validator.registerValidatorModule(val);
validator.registerValidatorModule(myInstanceValidator);
validator.registerValidatorModule(new IdChecker(theMode));

View File

@ -1,21 +1,20 @@
package ca.uhn.fhir.jpa.config;
import java.util.Properties;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import org.apache.commons.dbcp2.BasicDataSource;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Lazy;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.Properties;
@Configuration
@EnableTransactionManagement()
@ -65,4 +64,19 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
return extraProperties;
}
/**
* Bean which validates incoming requests
*/
@Bean
@Lazy
public RequestValidatingInterceptor requestValidatingInterceptor() {
RequestValidatingInterceptor requestValidator = new RequestValidatingInterceptor();
requestValidator.setFailOnSeverity(ResultSeverityEnum.ERROR);
requestValidator.setAddResponseHeaderOnSeverity(null);
requestValidator.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
requestValidator.addValidatorModule(instanceValidatorDstu2());
return requestValidator;
}
}

View File

@ -0,0 +1,34 @@
package ca.uhn.fhir.jpa.dao;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.HashSet;
import org.junit.Test;
public class DaoConfigTest {
@Test
public void testValidLogicalPattern() {
new DaoConfig().setTreatBaseUrlsAsLocal(new HashSet<String>(Arrays.asList("http://foo")));
new DaoConfig().setTreatBaseUrlsAsLocal(new HashSet<String>(Arrays.asList("http://foo*")));
}
@Test
public void testInvalidLogicalPattern() {
try {
new DaoConfig().setTreatBaseUrlsAsLocal(new HashSet<String>(Arrays.asList("http://*foo")));
fail();
} catch (IllegalArgumentException e) {
assertEquals("Base URL wildcard character (*) can only appear at the end of the string: http://*foo", e.getMessage());
}
try {
new DaoConfig().setTreatBaseUrlsAsLocal(new HashSet<String>(Arrays.asList("http://foo**")));
fail();
} catch (IllegalArgumentException e) {
assertEquals("Base URL wildcard character (*) can only appear at the end of the string: http://foo**", e.getMessage());
}
}
}

View File

@ -65,28 +65,12 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu2Test.class);
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
@After
public final void after() {
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
myDaoConfig.setTreatReferencesAsLogical(new DaoConfig().getTreatReferencesAsLogical());
}
/**
* See #534
*/
@Test
public void testBuiltInLogicalReferences() throws IOException {
myDaoConfig.getTreatReferencesAsLogical().add("http://phr.kanta.fi/fiphr-vs-*");
ValueSet vsBodySite = loadResourceFromClasspath(ValueSet.class, "/issue534/fiphr-vs-bodysite.xml");
myValueSetDao.create(vsBodySite, mySrd);
ValueSet vsObsMethod = loadResourceFromClasspath(ValueSet.class, "/issue534/fiphr-vs-observationmethod.xml");
myValueSetDao.create(vsObsMethod, mySrd);
StructureDefinition sd = loadResourceFromClasspath(StructureDefinition.class, "/issue534/bw_profile_snapshot.xml");
myStructureDefinitionDao.create(sd, mySrd);
}
private void assertGone(IIdType theId) {
try {
assertNotGone(theId);
@ -96,82 +80,13 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
}
}
@After
public final void after() {
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
myDaoConfig.setTreatReferencesAsLogical(new DaoConfig().getTreatReferencesAsLogical());
}
@Test
public void testValidateAgainstDstu2Profile() throws Exception {
myDaoConfig.setAllowExternalReferences(true);
String stream = IOUtils.toString(getClass().getResourceAsStream("/binu_testpatient_structuredefinition_dstu2.xml"), StandardCharsets.UTF_8);
StructureDefinition sd = myFhirCtx.newXmlParser().parseResource(StructureDefinition.class, stream);
myStructureDefinitionDao.create(sd, mySrd);
String rawResource = IOUtils.toString(getClass().getResourceAsStream("/binu_testpatient_resource.json"), StandardCharsets.UTF_8);
try {
myValueSetDao.validate(null, null, rawResource, EncodingEnum.JSON, ValidationModeEnum.UPDATE, null, mySrd);
fail();
} catch (PreconditionFailedException e) {
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome()));
}
}
@Test
public void testCreateBundleAllowsDocumentAndCollection() {
String methodName = "testCreateBundleAllowsDocumentAndCollection";
Patient p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue(methodName);
IIdType pid = myPatientDao.create(p, mySrd).getId();
p.setId(pid);
ourLog.info("Created patient, got it: {}", pid);
Bundle bundle = new Bundle();
bundle.setType((BundleTypeEnum)null);
bundle.addEntry().setResource(p).setFullUrl(pid.toUnqualifiedVersionless().getValue());
try {
myBundleDao.create(bundle, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type of: (missing)", e.getMessage());
}
bundle = new Bundle();
bundle.setType(BundleTypeEnum.BATCH_RESPONSE);
bundle.addEntry().setResource(p).setFullUrl(pid.toUnqualifiedVersionless().getValue());
try {
myBundleDao.create(bundle, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type of: batch-response", e.getMessage());
}
bundle = new Bundle();
bundle.setType(BundleTypeEnum.COLLECTION);
bundle.addEntry().setResource(p).setFullUrl(pid.toUnqualifiedVersionless().getValue());
myBundleDao.create(bundle, mySrd);
bundle = new Bundle();
bundle.setType(BundleTypeEnum.DOCUMENT);
bundle.addEntry().setResource(p).setFullUrl(pid.toUnqualifiedVersionless().getValue());
myBundleDao.create(bundle, mySrd);
}
/**
* This gets called from assertGone too! Careful about exceptions...
*/
private void assertNotGone(IIdType theId) {
if ("Patient".equals(theId.getResourceType())) {
myPatientDao.read(theId, mySrd);
} else if ("Organization".equals(theId.getResourceType())){
} else if ("Organization".equals(theId.getResourceType())) {
myOrganizationDao.read(theId, mySrd);
} else {
fail("No type");
@ -188,7 +103,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
}
private String log(IBundleProvider theHistory) {
StringBuilder b =new StringBuilder(theHistory.size() + " results: ");
StringBuilder b = new StringBuilder(theHistory.size() + " results: ");
for (IBaseResource next : theHistory.getResources(0, theHistory.size())) {
b.append("\n ").append(next.getIdElement().toUnqualified().getValue());
}
@ -233,6 +148,23 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
return retVal;
}
/**
* See #534
*/
@Test
public void testBuiltInLogicalReferences() throws IOException {
myDaoConfig.getTreatReferencesAsLogical().add("http://phr.kanta.fi/fiphr-vs-*");
ValueSet vsBodySite = loadResourceFromClasspath(ValueSet.class, "/issue534/fiphr-vs-bodysite.xml");
myValueSetDao.create(vsBodySite, mySrd);
ValueSet vsObsMethod = loadResourceFromClasspath(ValueSet.class, "/issue534/fiphr-vs-observationmethod.xml");
myValueSetDao.create(vsObsMethod, mySrd);
// Just make sure this saves
StructureDefinition sd = loadResourceFromClasspath(StructureDefinition.class, "/issue534/bw_profile_snapshot.xml");
myStructureDefinitionDao.create(sd, mySrd);
}
@Test
public void testCantSearchForDeletedResourceByLanguageOrTag() {
String methodName = "testCantSearchForDeletedResourceByLanguageOrTag";
@ -384,6 +316,48 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
}
}
@Test
public void testCreateBundleAllowsDocumentAndCollection() {
String methodName = "testCreateBundleAllowsDocumentAndCollection";
Patient p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue(methodName);
IIdType pid = myPatientDao.create(p, mySrd).getId();
p.setId(pid);
ourLog.info("Created patient, got it: {}", pid);
Bundle bundle = new Bundle();
bundle.setType((BundleTypeEnum) null);
bundle.addEntry().setResource(p).setFullUrl(pid.toUnqualifiedVersionless().getValue());
try {
myBundleDao.create(bundle, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type of: (missing)", e.getMessage());
}
bundle = new Bundle();
bundle.setType(BundleTypeEnum.BATCH_RESPONSE);
bundle.addEntry().setResource(p).setFullUrl(pid.toUnqualifiedVersionless().getValue());
try {
myBundleDao.create(bundle, mySrd);
fail();
} catch (UnprocessableEntityException e) {
assertEquals("Unable to store a Bundle resource on this server with a Bundle.type of: batch-response", e.getMessage());
}
bundle = new Bundle();
bundle.setType(BundleTypeEnum.COLLECTION);
bundle.addEntry().setResource(p).setFullUrl(pid.toUnqualifiedVersionless().getValue());
myBundleDao.create(bundle, mySrd);
bundle = new Bundle();
bundle.setType(BundleTypeEnum.DOCUMENT);
bundle.addEntry().setResource(p).setFullUrl(pid.toUnqualifiedVersionless().getValue());
myBundleDao.create(bundle, mySrd);
}
@Test
public void testCreateOperationOutcome() {
/*
@ -818,8 +792,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
assertThat(found, empty());
}
@Test
public void testDeleteResource() {
int initialHistory = myPatientDao.history(null, null, mySrd).size();
@ -882,7 +855,6 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
assertEquals(0, patients.size());
}
@Test
public void testDeleteThenUndelete() {
@ -917,7 +889,6 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
assertEquals(id2, gotId);
}
@Test
public void testDeleteWithMatchUrl() {
String methodName = "testDeleteWithMatchUrl";
@ -974,23 +945,24 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
myPatientDao.deleteByUrl("Patient?organization.identifier=http://example.com|" + methodName, mySrd);
assertGone(id);
assertNotGone(orgId);
myOrganizationDao.deleteByUrl("Organization?identifier=http://example.com|" + methodName, mySrd);
assertGone(id);
assertGone(orgId);
}
@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, mySrd).getId().toUnqualifiedVersionless();
Patient p = new Patient();
@ -1002,7 +974,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
myPatientDao.deleteByUrl("Patient?organization._profile=http://foo", mySrd);
assertGone(id);
myOrganizationDao.deleteByUrl("Organization?_profile=http://foo", mySrd);
try {
myOrganizationDao.read(orgId, mySrd);
@ -1027,8 +999,6 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
}
@Test
public void testDeleteWithMatchUrlChainedString() {
String methodName = "testDeleteWithMatchUrlChainedString";
@ -1055,11 +1025,11 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
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, mySrd).getId().toUnqualifiedVersionless();
Patient p = new Patient();
@ -1071,7 +1041,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
myPatientDao.deleteByUrl("Patient?organization._tag=http://foo|term", mySrd);
assertGone(id);
myOrganizationDao.deleteByUrl("Organization?_tag=http://foo|term", mySrd);
try {
myOrganizationDao.read(orgId, mySrd);
@ -1116,9 +1086,9 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
/*
* Org 2 has a name
*/
Organization org2 = new Organization();
org2.setName(methodName);
org2.setName(methodName);
org2.addIdentifier().setValue(methodName);
IIdType org2Id = myOrganizationDao.create(org2, mySrd).getId().toUnqualifiedVersionless();
@ -1131,13 +1101,13 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
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", mySrd);
assertGone(patId1);
assertNotGone(patId2);
assertNotGone(org1Id);
assertNotGone(org2Id);
myOrganizationDao.deleteByUrl("Organization?name:missing=true", mySrd);
assertGone(patId1);
assertNotGone(patId2);
@ -1149,7 +1119,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
assertGone(patId2);
assertGone(org1Id);
assertNotGone(org2Id);
myOrganizationDao.deleteByUrl("Organization?name:missing=false", mySrd);
assertGone(patId1);
assertGone(patId2);
@ -1186,13 +1156,13 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
String methodName = "testHistoryOverMultiplePages";
/*
for (int i = 0; i < 1000; i++) {
Patient patient = new Patient();
patient.addName().addFamily(methodName + "__" + i);
myPatientDao.create(patient).getId().toUnqualifiedVersionless();
}
*/
* for (int i = 0; i < 1000; i++) {
* Patient patient = new Patient();
* patient.addName().addFamily(methodName + "__" + i);
* myPatientDao.create(patient).getId().toUnqualifiedVersionless();
* }
*/
Patient patient = new Patient();
patient.addName().addFamily(methodName);
IIdType id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
@ -2976,4 +2946,27 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
}
}
@Test
public void testValidateAgainstDstu2Profile() throws Exception {
myDaoConfig.setAllowExternalReferences(true);
String stream = IOUtils.toString(getClass().getResourceAsStream("/binu_testpatient_structuredefinition_dstu2.xml"), StandardCharsets.UTF_8);
StructureDefinition sd = myFhirCtx.newXmlParser().parseResource(StructureDefinition.class, stream);
myStructureDefinitionDao.create(sd, mySrd);
String rawResource = IOUtils.toString(getClass().getResourceAsStream("/binu_testpatient_resource.json"), StandardCharsets.UTF_8);
try {
myValueSetDao.validate(null, null, rawResource, EncodingEnum.JSON, ValidationModeEnum.UPDATE, null, mySrd);
fail();
} catch (PreconditionFailedException e) {
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome()));
}
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -23,6 +23,7 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@ -82,6 +83,7 @@ import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Ignore;
import org.junit.Test;
@ -89,10 +91,7 @@ import org.mockito.ArgumentCaptor;
import com.google.common.collect.Lists;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
import ca.uhn.fhir.model.api.IQueryParameterType;
@ -129,6 +128,12 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3Test.class);
@After
public final void after() {
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
myDaoConfig.setTreatReferencesAsLogical(new DaoConfig().getTreatReferencesAsLogical());
}
private void assertGone(IIdType theId) {
try {
assertNotGone(theId);
@ -152,7 +157,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
fail("Can't handle type: " + theId.getResourceType());
}
}
private List<String> extractNames(IBundleProvider theSearch) {
ArrayList<String> retVal = new ArrayList<String>();
for (IBaseResource next : theSearch.getResources(0, theSearch.size())) {
@ -167,7 +172,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
retVal.addCoding().setSystem(theSystem).setCode(theCode);
return retVal;
}
private void sort(ArrayList<Coding> thePublished) {
ArrayList<Coding> tags = new ArrayList<Coding>(thePublished);
Collections.sort(tags, new Comparator<Coding>() {
@ -194,7 +199,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
}
});
}
private List<UriType> sortIds(List<UriType> theProfiles) {
ArrayList<UriType> retVal = new ArrayList<UriType>(theProfiles);
Collections.sort(retVal, new Comparator<UriType>() {
@ -288,21 +293,21 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
Encounter enc = new Encounter();
enc.getPeriod().setStartElement(new DateTimeType("2016-05-10")).setEndElement(new DateTimeType("2016-05-20"));
String id = myEncounterDao.create(enc, mySrd).getId().toUnqualifiedVersionless().getValue();
List<String> ids;
/*
* This should not match, per the definition of eq
*/
ids = toUnqualifiedVersionlessIdValues(myEncounterDao.search(Encounter.SP_DATE, new DateParam("2016-05-15")));
assertThat(ids, empty());
ids = toUnqualifiedVersionlessIdValues(myEncounterDao.search(Encounter.SP_DATE, new DateParam("eq2016-05-15")));
assertThat(ids, empty());
// Should match
ids = toUnqualifiedVersionlessIdValues(myEncounterDao.search(Encounter.SP_DATE, new DateParam("eq2016")));
assertThat(ids, contains(id));
@ -468,9 +473,9 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
CodeSystem cs = new CodeSystem();
cs.setStatus(PublicationStatus.DRAFT);
IIdType id = myCodeSystemDao.create(cs, mySrd).getId().toUnqualifiedVersionless();
myCodeSystemDao.delete(id, mySrd);
assertGone(id.toUnqualifiedVersionless());
}
@ -544,7 +549,6 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
}
@Test
public void testCreateDifferentTypesWithSameForcedId() {
String idName = "forcedId";
@ -1047,7 +1051,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
@Test
public void testDeleteResource() {
int initialHistory = myPatientDao.history((Date)null, null, mySrd).size();
int initialHistory = myPatientDao.history((Date) null, null, mySrd).size();
IIdType id1;
IIdType id2;
@ -1089,7 +1093,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
// good
}
IBundleProvider history = myPatientDao.history((Date)null, null, mySrd);
IBundleProvider history = myPatientDao.history((Date) null, null, mySrd);
assertEquals(4 + initialHistory, history.size());
List<IBaseResource> resources = history.getResources(0, 4);
assertNotNull(ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) resources.get(0)));
@ -1146,7 +1150,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
Observation obs1 = new Observation();
obs1.setStatus(ObservationStatus.FINAL);
IIdType obs1id = myObservationDao.create(obs1).getId().toUnqualifiedVersionless();
Observation obs2 = new Observation();
obs2.setStatus(ObservationStatus.FINAL);
IIdType obs2id = myObservationDao.create(obs2).getId().toUnqualifiedVersionless();
@ -1155,19 +1159,19 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
rpt.addIdentifier().setSystem("foo").setValue("IDENTIFIER");
rpt.addResult(new Reference(obs2id));
myDiagnosticReportDao.create(rpt).getId().toUnqualifiedVersionless();
myObservationDao.read(obs1id);
myObservationDao.read(obs2id);
try {
myObservationDao.deleteByUrl("Observation?_has:DiagnosticReport:result:identifier=foo|IDENTIFIER", mySrd);
fail();
} catch (ResourceVersionConflictException e) {
assertConflictException(e);
}
myObservationDao.read(obs1id);
myObservationDao.read(obs2id);
myObservationDao.read(obs2id);
}
@Test
@ -1461,7 +1465,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
}
// By type
history = myPatientDao.history((Date)null, null, mySrd);
history = myPatientDao.history((Date) null, null, mySrd);
assertEquals(fullSize + 1, history.size());
for (int i = 0; i < fullSize; i++) {
String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue();
@ -1528,7 +1532,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
}
// By type
history = myPatientDao.history((Date)null, null, mySrd);
history = myPatientDao.history((Date) null, null, mySrd);
assertEquals(fullSize + 1, history.size());
for (int i = 0; i < fullSize; i++) {
String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue();
@ -1585,7 +1589,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
inPatient.getMeta().addProfile("http://example.com/1");
IIdType id = myPatientDao.create(inPatient, mySrd).getId().toUnqualifiedVersionless();
IBundleProvider history = myPatientDao.history((Date)null, null, mySrd);
IBundleProvider history = myPatientDao.history((Date) null, null, mySrd);
assertEquals(1, history.size());
Patient outPatient = (Patient) history.getResources(0, 1).get(0);
assertEquals("version1", inPatient.getName().get(0).getFamily());
@ -1599,7 +1603,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
inPatient.getMeta().addProfile("http://example.com/2");
myPatientDao.metaAddOperation(id, inPatient.getMeta(), mySrd);
history = myPatientDao.history((Date)null, null, mySrd);
history = myPatientDao.history((Date) null, null, mySrd);
assertEquals(1, history.size());
outPatient = (Patient) history.getResources(0, 1).get(0);
assertEquals("version1", inPatient.getName().get(0).getFamily());
@ -1615,7 +1619,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
inPatient.getName().get(0).setFamily("version2");
myPatientDao.update(inPatient, mySrd);
history = myPatientDao.history((Date)null, null, mySrd);
history = myPatientDao.history((Date) null, null, mySrd);
assertEquals(2, history.size());
outPatient = (Patient) history.getResources(0, 2).get(0);
assertEquals("version2", outPatient.getName().get(0).getFamily());
@ -1682,13 +1686,13 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
}
List<String> idValues;
idValues = toUnqualifiedIdValues(myPatientDao.history(id, preDates.get(0), preDates.get(3), mySrd));
assertThat(idValues, contains(ids.get(2), ids.get(1), ids.get(0)));
idValues = toUnqualifiedIdValues(myPatientDao.history(preDates.get(0), preDates.get(3), mySrd));
assertThat(idValues, contains(ids.get(2), ids.get(1), ids.get(0)));
idValues = toUnqualifiedIdValues(mySystemDao.history(preDates.get(0), preDates.get(3), mySrd));
assertThat(idValues, contains(ids.get(2), ids.get(1), ids.get(0)));
}
@ -1709,7 +1713,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
// No since
IBundleProvider history = myPatientDao.history((Date)null, null, mySrd);
IBundleProvider history = myPatientDao.history((Date) null, null, mySrd);
assertEquals(1, history.size());
Patient outPatient = (Patient) history.getResources(0, 1).get(0);
assertEquals("version1", inPatient.getName().get(0).getFamily());
@ -1731,7 +1735,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
assertEquals(0, history.size());
}
@Test
public void testHistoryWithInvalidId() throws Exception {
try {
@ -1815,11 +1819,11 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
@Test
public void testIndexConditionWithAllOnsetTypes() {
// DateTimeType.class, Age.class, Period.class, Range.class, StringType.class
Condition c0 = new Condition();
c0.setOnset(new DateTimeType("2011-01-01"));
myConditionDao.create(c0, mySrd).getId().toUnqualifiedVersionless();
Condition c1 = new Condition();
c1.setOnset(new Age().setValue(100L).setCode("AGECODE"));
myConditionDao.create(c1, mySrd).getId().toUnqualifiedVersionless();
@ -1835,7 +1839,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
Condition c4 = new Condition();
c4.setOnset(new StringType("FOO"));
myConditionDao.create(c4, mySrd).getId().toUnqualifiedVersionless();
}
}
@Test
public void testInstanceMetaOperations() {
@ -2011,6 +2015,27 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
}
}
/**
* See #534
*/
@Test
public void testLogicalReferencesAreSearchable() throws IOException {
myDaoConfig.setTreatReferencesAsLogical(null);
myDaoConfig.addTreatReferencesAsLogical("http://foo.com/identifier*");
Patient p1 = new Patient();
p1.getManagingOrganization().setReference("http://foo.com/identifier/1");
String p1id = myPatientDao.create(p1, mySrd).getId().toUnqualifiedVersionless().getValue();
Patient p2 = new Patient();
p2.getManagingOrganization().setReference("http://foo.com/identifier/2");
String p2id = myPatientDao.create(p2, mySrd).getId().toUnqualifiedVersionless().getValue();
IBundleProvider found = myPatientDao.search(Patient.SP_ORGANIZATION, new ReferenceParam("http://foo.com/identifier/1"));
assertThat(toUnqualifiedVersionlessIdValues(found), contains(p1id));
assertThat(toUnqualifiedVersionlessIdValues(found), not(contains(p2id)));
}
@Test
public void testOrganizationName() {
@ -3297,7 +3322,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
Date before = new DateTimeType("2011-01-01T10:00:00Z").getValue();
Date middle = new DateTimeType("2011-01-02T10:00:00Z").getValue();
Date after = new DateTimeType("2011-01-03T10:00:00Z").getValue();
CarePlan cp = new CarePlan();
cp.addActivity().getDetail().setScheduled(new Timing().addEvent(before).addEvent(middle).addEvent(after));
cp.addActivity().getDetail();
@ -3307,9 +3332,9 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
cp2.addActivity().getDetail().setScheduled(new StringType("FOO"));
cp2.addActivity().getDetail();
myCarePlanDao.create(cp2, mySrd).getId().toUnqualifiedVersionless();
SearchParameterMap params;
params = new SearchParameterMap();
params.add(CarePlan.SP_ACTIVITY_DATE, new DateRangeParam("2010-01-01T10:00:00Z", null));
assertThat(toUnqualifiedVersionlessIdValues(myCarePlanDao.search(params)), contains(id.getValue()));
@ -3317,7 +3342,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
params = new SearchParameterMap();
params.add(CarePlan.SP_ACTIVITY_DATE, new DateRangeParam("2011-01-01T10:00:00Z", null));
assertThat(toUnqualifiedVersionlessIdValues(myCarePlanDao.search(params)), contains(id.getValue()));
params = new SearchParameterMap();
params.add(CarePlan.SP_ACTIVITY_DATE, new DateRangeParam("2012-01-01T10:00:00Z", null));
assertThat(toUnqualifiedVersionlessIdValues(myCarePlanDao.search(params)), empty());
@ -3364,7 +3389,8 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
}
public static void assertConflictException(ResourceVersionConflictException e) {
assertThat(e.getMessage(), matchesPattern("Unable to delete [a-zA-Z]+/[0-9]+ because at least one resource has a reference to this resource. First reference found was resource [a-zA-Z]+/[0-9]+ in path [a-zA-Z]+.[a-zA-Z]+"));
assertThat(e.getMessage(), matchesPattern(
"Unable to delete [a-zA-Z]+/[0-9]+ because at least one resource has a reference to this resource. First reference found was resource [a-zA-Z]+/[0-9]+ in path [a-zA-Z]+.[a-zA-Z]+"));
}
private static List<String> toStringList(List<UriType> theUriType) {

View File

@ -0,0 +1,56 @@
package ca.uhn.fhir.jpa.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.to.FhirTesterMvcConfig;
import ca.uhn.fhir.to.TesterConfig;
//@formatter:off
/**
* This spring config file configures the web testing module. It serves two
* purposes:
* 1. It imports FhirTesterMvcConfig, which is the spring config for the
* tester itself
* 2. It tells the tester which server(s) to talk to, via the testerConfig()
* method below
*/
@Configuration
@Import(FhirTesterMvcConfig.class)
public class FhirTesterConfigDstu3 {
/**
* This bean tells the testing webpage which servers it should configure itself
* to communicate with. In this example we configure it to talk to the local
* server, as well as one public server. If you are creating a project to
* deploy somewhere else, you might choose to only put your own server's
* address here.
*
* Note the use of the ${serverBase} variable below. This will be replaced with
* the base URL as reported by the server itself. Often for a simple Tomcat
* (or other container) installation, this will end up being something
* like "http://localhost:8080/hapi-fhir-jpaserver-example". If you are
* deploying your server to a place with a fully qualified domain name,
* you might want to use that instead of using the variable.
*/
@Bean
public TesterConfig testerConfig() {
TesterConfig retVal = new TesterConfig();
retVal
.addServer()
.withId("home")
.withFhirVersion(FhirVersionEnum.DSTU3)
.withBaseUrl("${serverBase}/baseDstu3")
.withName("Local Tester")
.addServer()
.withId("hapi")
.withFhirVersion(FhirVersionEnum.DSTU3)
.withBaseUrl("http://fhirtest.uhn.ca/baseDstu3")
.withName("Public HAPI Test Server");
return retVal;
}
}
//@formatter:on

View File

@ -46,22 +46,20 @@ public class JpaServerDemo extends RestfulServer {
protected void initialize() throws ServletException {
super.initialize();
/*
/*
* We want to support FHIR DSTU2 format. This means that the server
* will use the DSTU2 bundle format and other DSTU2 encoding changes.
*
* If you want to use DSTU1 instead, change the following line, and
* change the 2 occurrences of dstu2 in web.xml to dstu1
*/
FhirVersionEnum fhirVersion = FhirVersionEnum.DSTU2;
FhirContext context = new FhirContext(fhirVersion);
setFhirContext(context);
FhirVersionEnum fhirVersion = FhirVersionEnum.DSTU3;
setFhirContext(new FhirContext(fhirVersion));
// Get the spring context from the web container (it's declared in web.xml)
myAppCtx = ContextLoaderListener.getCurrentWebApplicationContext();
/*
/*
* The BaseJavaConfigDstu2.java class is a spring configuration
* file which is automatically generated as a part of hapi-fhir-jpaserver-base and
* contains bean definitions for a resource provider for each resource type
@ -78,8 +76,8 @@ public class JpaServerDemo extends RestfulServer {
}
List<IResourceProvider> beans = myAppCtx.getBean(resourceProviderBeanName, List.class);
setResourceProviders(beans);
/*
/*
* The system provider implements non-resource-type methods, such as
* transaction, and global history.
*/
@ -174,15 +172,15 @@ public class JpaServerDemo extends RestfulServer {
}
/*
* If you are hosting this server at a specific DNS name, the server will try to
* If you are hosting this server at a specific DNS name, the server will try to
* figure out the FHIR base URL based on what the web container tells it, but
* this doesn't always work. If you are setting links in your search bundles that
* just refer to "localhost", you might want to use a server address strategy:
*/
//setServerAddressStrategy(new HardcodedServerAddressStrategy("http://mydomain.com/fhir/baseDstu2"));
/*
* If you are using DSTU3+, you may want to add a terminology uploader, which allows
* If you are using DSTU3+, you may want to add a terminology uploader, which allows
* uploading of external terminologies such as Snomed CT. Note that this uploader
* does not have any security attached (any anonymous user may use it by default)
* so it is a potential security vulnerability. Consider using an AuthorizationInterceptor

View File

@ -13,7 +13,7 @@
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
ca.uhn.fhir.jpa.demo.FhirServerConfig
ca.uhn.fhir.jpa.demo.FhirServerConfigDstu3
</param-value>
</context-param>
@ -28,7 +28,7 @@
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>ca.uhn.fhir.jpa.demo.FhirTesterConfig</param-value>
<param-value>ca.uhn.fhir.jpa.demo.FhirTesterConfigDstu3</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
@ -42,14 +42,14 @@
</init-param>
<init-param>
<param-name>FhirVersion</param-name>
<param-value>DSTU2</param-value>
<param-value>DSTU3</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>fhirServlet</servlet-name>
<url-pattern>/baseDstu2/*</url-pattern>
<url-pattern>/baseDstu3/*</url-pattern>
</servlet-mapping>
<servlet-mapping>

View File

@ -7,13 +7,13 @@ import java.io.IOException;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
@ -21,7 +21,7 @@ import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
public class ExampleServerIT {
private static IGenericClient ourClient;
private static FhirContext ourCtx = FhirContext.forDstu2();
private static FhirContext ourCtx = FhirContext.forDstu3();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerIT.class);
private static int ourPort;
@ -34,11 +34,11 @@ public class ExampleServerIT {
String methodName = "testCreateResourceConditional";
Patient pt = new Patient();
pt.addName().addFamily(methodName);
pt.addName().setFamily(methodName);
IIdType id = ourClient.create().resource(pt).execute().getId();
Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute();
assertEquals(methodName, pt2.getName().get(0).getFamily().get(0).getValue());
assertEquals(methodName, pt2.getName().get(0).getFamily());
}
@AfterClass
@ -72,7 +72,7 @@ public class ExampleServerIT {
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
ourServerBase = "http://localhost:" + ourPort + "/baseDstu2";
ourServerBase = "http://localhost:" + ourPort + "/baseDstu3";
ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
ourClient.registerInterceptor(new LoggingInterceptor(true));

View File

@ -1,10 +1,14 @@
package ca.uhn.fhirtest.config;
import java.util.Properties;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhirtest.interceptor.TdlSecurityInterceptor;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.dialect.DerbyTenSevenDialect;
@ -13,18 +17,16 @@ import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhirtest.interceptor.TdlSecurityInterceptor;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.Properties;
@Configuration
@Import(CommonConfig.class)
@ -113,6 +115,49 @@ public class TdlDstu2Config extends BaseJavaConfigDstu2 {
return extraProperties;
}
/**
* Bean which validates incoming requests
*/
@Bean
@Lazy
public RequestValidatingInterceptor requestValidatingInterceptor() {
RequestValidatingInterceptor requestValidator = new RequestValidatingInterceptor();
requestValidator.setFailOnSeverity(null);
requestValidator.setAddResponseHeaderOnSeverity(null);
requestValidator.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
requestValidator.addValidatorModule(instanceValidatorDstu2());
requestValidator.setIgnoreValidatorExceptions(true);
return requestValidator;
}
/**
* Bean which validates outgoing responses
*/
@Bean
@Lazy
public ResponseValidatingInterceptor responseValidatingInterceptor() {
ResponseValidatingInterceptor responseValidator = new ResponseValidatingInterceptor();
responseValidator.setResponseHeaderValueNoIssues("Validation did not detect any issues");
responseValidator.setFailOnSeverity(null);
responseValidator.setAddResponseHeaderOnSeverity(null);
responseValidator.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
responseValidator.addExcludeOperationType(RestOperationTypeEnum.METADATA);
responseValidator.addExcludeOperationType(RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE);
responseValidator.addExcludeOperationType(RestOperationTypeEnum.EXTENDED_OPERATION_SERVER);
responseValidator.addExcludeOperationType(RestOperationTypeEnum.EXTENDED_OPERATION_TYPE);
responseValidator.addExcludeOperationType(RestOperationTypeEnum.GET_PAGE);
responseValidator.addExcludeOperationType(RestOperationTypeEnum.HISTORY_INSTANCE);
responseValidator.addExcludeOperationType(RestOperationTypeEnum.HISTORY_SYSTEM);
responseValidator.addExcludeOperationType(RestOperationTypeEnum.HISTORY_TYPE);
responseValidator.addExcludeOperationType(RestOperationTypeEnum.SEARCH_SYSTEM);
responseValidator.addExcludeOperationType(RestOperationTypeEnum.SEARCH_TYPE);
responseValidator.addValidatorModule(instanceValidatorDstu2());
responseValidator.setIgnoreValidatorExceptions(true);
return responseValidator;
}
@Bean(autowire=Autowire.BY_TYPE)
public IServerInterceptor subscriptionSecurityInterceptor() {
return new SubscriptionsRequireManualActivationInterceptorDstu2();

View File

@ -1,30 +1,28 @@
package ca.uhn.fhirtest.config;
import java.util.Properties;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.dialect.PostgreSQL94Dialect;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.Properties;
@Configuration
@Import(CommonConfig.class)
@ -114,6 +112,22 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
return extraProperties;
}
/**
* Bean which validates incoming requests
*/
@Bean
@Lazy
public RequestValidatingInterceptor requestValidatingInterceptor() {
RequestValidatingInterceptor requestValidator = new RequestValidatingInterceptor();
requestValidator.setFailOnSeverity(null);
requestValidator.setAddResponseHeaderOnSeverity(null);
requestValidator.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
requestValidator.addValidatorModule(instanceValidatorDstu2());
requestValidator.setIgnoreValidatorExceptions(true);
return requestValidator;
}
// @Bean(autowire = Autowire.BY_TYPE)
// public IServerInterceptor subscriptionSecurityInterceptor() {
// return new SubscriptionsRequireManualActivationInterceptorDstu2();

View File

@ -26,6 +26,27 @@
Web testing UI displayed an error when a transaction was pasted into the UI
for a DSTU2 server. Thanks to Suresh Kumar for reporting!
</action>
<action type="add">
DaoConfig#setAllowInlineMatchUrlReferences() now defaults to
<![CDATA[<code>true</code>]]> since inline conditional references
are now a part of the FHIR specification. Thanks to Jan Dědek for
pointing this out!
</action>
<action type="add" issue="609">
hapi-fhir-jpaserver-base now exposes a
<![CDATA[<code>FhirInstanceValidator</code> bean named <code>"myInstanceValidatorDstu2"</code>]]>
for DSTU2. A similar bean for DSTU3 was previously implemented.
</action>
<action type="add" issue="453">
hapi-fhir-jpaserver-example project now defaults to STU3 mode instead of
the previous DSTU2. Thanks to Joel Schneider for the pull request!
</action>
<action type="add" issue="534">
JPA server now has a setting on the DaoConfig to force it to treat
certain reference URLs or reference URL patterns as logical URLs instead
of literal ones, meaning that the server will not try to resolve these
URLs. Thanks to Eeva Turkka for the suggestion!
</action>
</release>
<release version="2.3" date="2017-03-18">
<action type="add">

View File

@ -174,6 +174,38 @@ public DaoConfig daoConfig() {
return retVal;
}]]></source>
</subsection>
<subsection name="Logical References">
<p>
In some cases, you may have references which are <i>Logical References</i>,
which means that they act as an identifier and not necessarily as a literal
web address.
</p>
<p>
A common use for logical references is in references to conformance
resources, such as ValueSets, StructureDefinitions, etc. For example,
you might refer to the ValueSet
<code>http://hl7.org/fhir/ValueSet/quantity-comparator</code>
from your own resources. In this case, you are not neccesarily telling
the server that this is a real address that it should resolve, but
rather that this is an identifier for a ValueSet where
<code>ValueSet.url</code> has the given URI/URL.
</p>
<p>
HAPI can be configured to treat certain URI/URL patterns as
logical by using the DaoConfig#setTreatReferencesAsLogical property
(see <a href="./apidocs-jpaserver/ca/uhn/fhir/jpa/dao/DaoConfig.html#setTreatReferencesAsLogical-java.util.Set-">JavaDoc</a>).
For example:
</p>
<code>
// Treat specific URL as logical
myDaoConfig.getTreatReferencesAsLogical().add("http://mysystem.com/ValueSet/cats-and-dogs");
// Treat all references with given prefix as logical
myDaoConfig.getTreatReferencesAsLogical().add("http://mysystem.com/mysystem-vs-*");
</code>
</subsection>
</section>