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 * 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> * param such as <code>new StringClientParam("foo")</code>
*/ */
IQuery<T> descending(IParam theParam); 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 * A bean containing details about the request that is about to be processed, including
* @param theResponseObject * @param theResponseObject
* The actual object which is being streamed to the client as a response * 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. * @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 * 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 * 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 * 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. * 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 * 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% * #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.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; 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.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement; 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 @Configuration
@EnableTransactionManagement @EnableTransactionManagement
public class BaseDstu2Config extends BaseConfig { public class BaseDstu2Config extends BaseConfig {
@ -75,6 +74,15 @@ public class BaseDstu2Config extends BaseConfig {
return retVal; 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) @Bean(autowire = Autowire.BY_TYPE)
public IFulltextSearchSvc searchDao() { public IFulltextSearchSvc searchDao() {
FulltextSearchSvcImpl searchDao = new FulltextSearchSvcImpl(); 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 (isLogicalReference(nextId)) {
if (treatReferencesAsLogical != null) { ResourceLink resourceLink = new ResourceLink(nextPathAndRef.getPath(), theEntity, nextId, theUpdateTime);
boolean isLogical = false; if (theLinks.add(resourceLink)) {
for (String nextLogicalRef : treatReferencesAsLogical) { ourLog.info("Indexing remote resource reference URL: {}", nextId);
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;
} }
continue;
} }
String baseUrl = nextId.getBaseUrl(); String baseUrl = nextId.getBaseUrl();
String typeString = nextId.getResourceType(); String typeString = nextId.getResourceType();
if (isBlank(typeString)) { 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) { protected Set<ResourceIndexedSearchParamCoords> extractSearchParamCoords(ResourceTable theEntity, IBaseResource theResource) {
return mySearchParamExtractor.extractSearchParamCoords(theEntity, 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. * Subclasses may override to provide behaviour. Called when a resource has been inserted into the database for the first time.
* *
* @param theEntity * @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 * @param theTag
* The tag * The tag
* @return Returns <code>true</code> if the tag should be removed * @return Returns <code>true</code> if the tag should be removed
*/ */
@SuppressWarnings("unused")
protected void postPersist(ResourceTable theEntity, T theResource) { protected void postPersist(ResourceTable theEntity, T theResource) {
// nothing // 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 * Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database
* *
* @param theEntity * @param theEntity
* The resource * The resource
* @param theResource * @param theResource
* The resource being persisted * The resource being persisted
*/ */
protected void postUpdate(ResourceTable theEntity, T theResource) { protected void postUpdate(ResourceTable theEntity, T theResource) {
// nothing // nothing
@ -998,7 +1002,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
return ids; return ids;
} }
@SuppressWarnings("unused")
@CoverageIgnore @CoverageIgnore
public BaseHasResource readEntity(IIdType theValueId) { public BaseHasResource readEntity(IIdType theValueId) {
throw new NotImplementedException(""); throw new NotImplementedException("");
@ -1031,9 +1034,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* </p> * </p>
* *
* @param theEntity * @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 * @param theTag
* The tag * The tag
* @return Retturns <code>true</code> if the tag should be removed * @return Retturns <code>true</code> if the tag should be removed
*/ */
protected boolean shouldDroppedTagBeRemovedOnUpdate(ResourceTable theEntity, ResourceTag theTag) { protected boolean shouldDroppedTagBeRemovedOnUpdate(ResourceTable theEntity, ResourceTag theTag) {
@ -1269,7 +1272,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
setUpdatedTime(uriParams, theUpdateTime); setUpdatedTime(uriParams, theUpdateTime);
setUpdatedTime(coordsParams, theUpdateTime); setUpdatedTime(coordsParams, theUpdateTime);
setUpdatedTime(tokenParams, 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 * 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 * matching
@ -1531,11 +1534,14 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
for (IBase nextChild : values) { for (IBase nextChild : values) {
IBaseReference nextRef = (IBaseReference) nextChild; IBaseReference nextRef = (IBaseReference) nextChild;
if (!isBlank(nextRef.getReferenceElement().getResourceType())) { IIdType referencedId = nextRef.getReferenceElement();
if (!nextRef.getReferenceElement().getValue().contains("?")) { if (!isBlank(referencedId.getResourceType())) {
if (!validTypes.contains(nextRef.getReferenceElement().getResourceType())) { if (!isLogicalReference(referencedId)) {
throw new UnprocessableEntityException( if (!referencedId.getValue().contains("?")) {
"Invalid reference found at path '" + newPath + "'. Resource type '" + nextRef.getReferenceElement().getResourceType() + "' is not valid for this path"); 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. * "subsetted" tag and rejects resources which have it. Subclasses should call the superclass implementation to preserve this check.
* *
* @param theResource * @param theResource
* The resource that is about to be persisted * The resource that is about to be persisted
* @param theEntityToSave * @param theEntityToSave
* TODO * TODO
*/ */
protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) { protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) {
Object tag = null; Object tag = null;

View File

@ -56,7 +56,8 @@ public class DaoConfig {
// *** // ***
// update setter javadoc if default changes // update setter javadoc if default changes
// *** // ***
private boolean myAllowInlineMatchUrlReferences = false; private boolean myAllowInlineMatchUrlReferences = true;
private boolean myAllowMultipleDelete; private boolean myAllowMultipleDelete;
private boolean myDefaultSearchParamsCanBeOverridden = false; private boolean myDefaultSearchParamsCanBeOverridden = false;
// *** // ***
@ -93,6 +94,20 @@ public class DaoConfig {
private Set<String> myTreatBaseUrlsAsLocal = new HashSet<String>(); private Set<String> myTreatBaseUrlsAsLocal = new HashSet<String>();
private Set<String> myTreatReferencesAsLogical = new HashSet<String>(DEFAULT_LOGICAL_BASE_URLS); 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, * 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 * 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. * (next/prev links in search response bundles) will become invalid. Defaults to 1 hour.
* </p> * </p>
* <p> * <p>
* * <p>
* @see To disable this feature entirely, see {@link #setExpireSearchResults(boolean)} * To disable this feature entirely, see {@link #setExpireSearchResults(boolean)}
* </p> * </p>
* *
* @since 1.5 * @since 1.5
*/ */
@ -196,9 +211,9 @@ public class DaoConfig {
* references instead of being treated as real references. * references instead of being treated as real references.
* <p> * <p>
* A logical reference is a reference which is treated as an identifier, and * 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 * 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. * reference.
* </p> * </p>
* <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> * <li><code>http://example.com/some-base*</code> <b>(will match anything beginning with the part before the *)</b></li>
* </ul> * </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() { public Set<String> getTreatReferencesAsLogical() {
return myTreatReferencesAsLogical; 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 "Patient?identifier=12345", this is reference match URL will be resolved and replaced according
* to the usual match URL rules. * to the usual match URL rules.
* <p> * <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> * </p>
* *
* @since 1.5 * @since 1.5
@ -401,8 +418,10 @@ public class DaoConfig {
* </p> * </p>
* <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 * @since 1.5
*/ */
public void setExpireSearchResultsAfterMillis(long theExpireSearchResultsAfterMillis) { public void setExpireSearchResultsAfterMillis(long theExpireSearchResultsAfterMillis) {
@ -418,7 +437,7 @@ public class DaoConfig {
* paging provider instead. Deprecated in HAPI FHIR 2.3 (Jan 2017) * paging provider instead. Deprecated in HAPI FHIR 2.3 (Jan 2017)
*/ */
@Deprecated @Deprecated
public void setHardSearchLimit(@SuppressWarnings("unused") int theHardSearchLimit) { public void setHardSearchLimit(int theHardSearchLimit) {
// this method does nothing // this method does nothing
} }
@ -528,6 +547,12 @@ public class DaoConfig {
* means no references will be treated as external * means no references will be treated as external
*/ */
public void setTreatBaseUrlsAsLocal(Set<String> theTreatBaseUrlsAsLocal) { public void setTreatBaseUrlsAsLocal(Set<String> theTreatBaseUrlsAsLocal) {
if (theTreatBaseUrlsAsLocal != null) {
for (String next : theTreatBaseUrlsAsLocal) {
validateTreatBaseUrlsAsLocal(next);
}
}
HashSet<String> treatBaseUrlsAsLocal = new HashSet<String>(); HashSet<String> treatBaseUrlsAsLocal = new HashSet<String>();
for (String next : ObjectUtils.defaultIfNull(theTreatBaseUrlsAsLocal, new HashSet<String>())) { for (String next : ObjectUtils.defaultIfNull(theTreatBaseUrlsAsLocal, new HashSet<String>())) {
while (next.endsWith("/")) { while (next.endsWith("/")) {
@ -544,9 +569,9 @@ public class DaoConfig {
* references instead of being treated as real references. * references instead of being treated as real references.
* <p> * <p>
* A logical reference is a reference which is treated as an identifier, and * 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 * 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. * reference.
* </p> * </p>
* <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> * <li><code>http://example.com/some-base*</code> <b>(will match anything beginning with the part before the *)</b></li>
* </ul> * </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) { public DaoConfig setTreatReferencesAsLogical(Set<String> theTreatReferencesAsLogical) {
myTreatReferencesAsLogical = theTreatReferencesAsLogical; myTreatReferencesAsLogical = theTreatReferencesAsLogical;
return this; 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; 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.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;
@ -64,6 +26,38 @@ import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.IValidationContext; import ca.uhn.fhir.validation.IValidationContext;
import ca.uhn.fhir.validation.IValidatorModule; import ca.uhn.fhir.validation.IValidatorModule;
import ca.uhn.fhir.validation.ValidationResult; 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> { public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResourceDao<T> {
@ -71,6 +65,9 @@ public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResou
@Qualifier("myJpaValidationSupportDstu2") @Qualifier("myJpaValidationSupportDstu2")
private IValidationSupport myJpaValidationSupport; private IValidationSupport myJpaValidationSupport;
@Autowired()
@Qualifier("myInstanceValidatorDstu2")
private IValidatorModule myInstanceValidator;
@Override @Override
protected List<Object> getIncludeValues(FhirTerser theTerser, Include theInclude, IBaseResource theResource, RuntimeResourceDefinition theResourceDef) { 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(); FhirValidator validator = getContext().newValidator();
FhirInstanceValidator val = new FhirInstanceValidator(); validator.registerValidatorModule(myInstanceValidator);
val.setBestPracticeWarningLevel(BestPracticeWarningLevel.Warning);
val.setValidationSupport(new ValidationSupportChain(new DefaultProfileValidationSupport(), myJpaValidationSupport));
validator.registerValidatorModule(val);
validator.registerValidatorModule(new IdChecker(theMode)); validator.registerValidatorModule(new IdChecker(theMode));

View File

@ -1,21 +1,20 @@
package ca.uhn.fhir.jpa.config; package ca.uhn.fhir.jpa.config;
import java.util.Properties; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import javax.persistence.EntityManagerFactory; import ca.uhn.fhir.validation.ResultSeverityEnum;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
import org.hibernate.jpa.HibernatePersistenceProvider; import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; 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.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.EnableTransactionManagement;
import ca.uhn.fhir.context.FhirContext; import javax.persistence.EntityManagerFactory;
import ca.uhn.fhir.jpa.dao.DaoConfig; import javax.sql.DataSource;
import java.util.Properties;
@Configuration @Configuration
@EnableTransactionManagement() @EnableTransactionManagement()
@ -65,4 +64,19 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
return extraProperties; 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); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu2Test.class);
@AfterClass @After
public static void afterClassClearContext() { public final void after() {
TestUtil.clearAllStaticFieldsForUnitTest(); 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) { private void assertGone(IIdType theId) {
try { try {
assertNotGone(theId); 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... * This gets called from assertGone too! Careful about exceptions...
*/ */
private void assertNotGone(IIdType theId) { private void assertNotGone(IIdType theId) {
if ("Patient".equals(theId.getResourceType())) { if ("Patient".equals(theId.getResourceType())) {
myPatientDao.read(theId, mySrd); myPatientDao.read(theId, mySrd);
} else if ("Organization".equals(theId.getResourceType())){ } else if ("Organization".equals(theId.getResourceType())) {
myOrganizationDao.read(theId, mySrd); myOrganizationDao.read(theId, mySrd);
} else { } else {
fail("No type"); fail("No type");
@ -188,7 +103,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
} }
private String log(IBundleProvider theHistory) { 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())) { for (IBaseResource next : theHistory.getResources(0, theHistory.size())) {
b.append("\n ").append(next.getIdElement().toUnqualified().getValue()); b.append("\n ").append(next.getIdElement().toUnqualified().getValue());
} }
@ -233,6 +148,23 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
return retVal; 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 @Test
public void testCantSearchForDeletedResourceByLanguageOrTag() { public void testCantSearchForDeletedResourceByLanguageOrTag() {
String methodName = "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 @Test
public void testCreateOperationOutcome() { public void testCreateOperationOutcome() {
/* /*
@ -818,8 +792,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
assertThat(found, empty()); assertThat(found, empty());
} }
@Test @Test
public void testDeleteResource() { public void testDeleteResource() {
int initialHistory = myPatientDao.history(null, null, mySrd).size(); int initialHistory = myPatientDao.history(null, null, mySrd).size();
@ -882,7 +855,6 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
assertEquals(0, patients.size()); assertEquals(0, patients.size());
} }
@Test @Test
public void testDeleteThenUndelete() { public void testDeleteThenUndelete() {
@ -917,7 +889,6 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
assertEquals(id2, gotId); assertEquals(id2, gotId);
} }
@Test @Test
public void testDeleteWithMatchUrl() { public void testDeleteWithMatchUrl() {
String methodName = "testDeleteWithMatchUrl"; String methodName = "testDeleteWithMatchUrl";
@ -974,23 +945,24 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
myPatientDao.deleteByUrl("Patient?organization.identifier=http://example.com|" + methodName, mySrd); myPatientDao.deleteByUrl("Patient?organization.identifier=http://example.com|" + methodName, mySrd);
assertGone(id); assertGone(id);
assertNotGone(orgId); assertNotGone(orgId);
myOrganizationDao.deleteByUrl("Organization?identifier=http://example.com|" + methodName, mySrd); myOrganizationDao.deleteByUrl("Organization?identifier=http://example.com|" + methodName, mySrd);
assertGone(id); assertGone(id);
assertGone(orgId); assertGone(orgId);
} }
@Test @Test
public void testDeleteWithMatchUrlChainedProfile() { public void testDeleteWithMatchUrlChainedProfile() {
String methodName = "testDeleteWithMatchUrlChainedProfile"; String methodName = "testDeleteWithMatchUrlChainedProfile";
List<IdDt> profileList = new ArrayList<IdDt>(); List<IdDt> profileList = new ArrayList<IdDt>();
profileList.add(new IdDt("http://foo")); profileList.add(new IdDt("http://foo"));
Organization org = new Organization(); Organization org = new Organization();
ResourceMetadataKeyEnum.PROFILES.put(org, profileList); ResourceMetadataKeyEnum.PROFILES.put(org, profileList);
org.setName(methodName); org.setName(methodName);
IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
Patient p = new Patient(); Patient p = new Patient();
@ -1002,7 +974,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
myPatientDao.deleteByUrl("Patient?organization._profile=http://foo", mySrd); myPatientDao.deleteByUrl("Patient?organization._profile=http://foo", mySrd);
assertGone(id); assertGone(id);
myOrganizationDao.deleteByUrl("Organization?_profile=http://foo", mySrd); myOrganizationDao.deleteByUrl("Organization?_profile=http://foo", mySrd);
try { try {
myOrganizationDao.read(orgId, mySrd); myOrganizationDao.read(orgId, mySrd);
@ -1027,8 +999,6 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
} }
@Test @Test
public void testDeleteWithMatchUrlChainedString() { public void testDeleteWithMatchUrlChainedString() {
String methodName = "testDeleteWithMatchUrlChainedString"; String methodName = "testDeleteWithMatchUrlChainedString";
@ -1055,11 +1025,11 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
TagList tl = new TagList(); TagList tl = new TagList();
tl.addTag("http://foo", "term"); tl.addTag("http://foo", "term");
Organization org = new Organization(); Organization org = new Organization();
ResourceMetadataKeyEnum.TAG_LIST.put(org, tl); ResourceMetadataKeyEnum.TAG_LIST.put(org, tl);
org.setName(methodName); org.setName(methodName);
IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless(); IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
Patient p = new Patient(); Patient p = new Patient();
@ -1071,7 +1041,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
myPatientDao.deleteByUrl("Patient?organization._tag=http://foo|term", mySrd); myPatientDao.deleteByUrl("Patient?organization._tag=http://foo|term", mySrd);
assertGone(id); assertGone(id);
myOrganizationDao.deleteByUrl("Organization?_tag=http://foo|term", mySrd); myOrganizationDao.deleteByUrl("Organization?_tag=http://foo|term", mySrd);
try { try {
myOrganizationDao.read(orgId, mySrd); myOrganizationDao.read(orgId, mySrd);
@ -1116,9 +1086,9 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
/* /*
* Org 2 has a name * Org 2 has a name
*/ */
Organization org2 = new Organization(); Organization org2 = new Organization();
org2.setName(methodName); org2.setName(methodName);
org2.addIdentifier().setValue(methodName); org2.addIdentifier().setValue(methodName);
IIdType org2Id = myOrganizationDao.create(org2, mySrd).getId().toUnqualifiedVersionless(); 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("Org ID 1 : {}", org1Id);
ourLog.info("Pat ID 2 : {}", patId2); ourLog.info("Pat ID 2 : {}", patId2);
ourLog.info("Org ID 2 : {}", org2Id); ourLog.info("Org ID 2 : {}", org2Id);
myPatientDao.deleteByUrl("Patient?organization.name:missing=true", mySrd); myPatientDao.deleteByUrl("Patient?organization.name:missing=true", mySrd);
assertGone(patId1); assertGone(patId1);
assertNotGone(patId2); assertNotGone(patId2);
assertNotGone(org1Id); assertNotGone(org1Id);
assertNotGone(org2Id); assertNotGone(org2Id);
myOrganizationDao.deleteByUrl("Organization?name:missing=true", mySrd); myOrganizationDao.deleteByUrl("Organization?name:missing=true", mySrd);
assertGone(patId1); assertGone(patId1);
assertNotGone(patId2); assertNotGone(patId2);
@ -1149,7 +1119,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
assertGone(patId2); assertGone(patId2);
assertGone(org1Id); assertGone(org1Id);
assertNotGone(org2Id); assertNotGone(org2Id);
myOrganizationDao.deleteByUrl("Organization?name:missing=false", mySrd); myOrganizationDao.deleteByUrl("Organization?name:missing=false", mySrd);
assertGone(patId1); assertGone(patId1);
assertGone(patId2); assertGone(patId2);
@ -1186,13 +1156,13 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
String methodName = "testHistoryOverMultiplePages"; String methodName = "testHistoryOverMultiplePages";
/* /*
for (int i = 0; i < 1000; i++) { * for (int i = 0; i < 1000; i++) {
Patient patient = new Patient(); * Patient patient = new Patient();
patient.addName().addFamily(methodName + "__" + i); * patient.addName().addFamily(methodName + "__" + i);
myPatientDao.create(patient).getId().toUnqualifiedVersionless(); * myPatientDao.create(patient).getId().toUnqualifiedVersionless();
} * }
*/ */
Patient patient = new Patient(); Patient patient = new Patient();
patient.addName().addFamily(methodName); patient.addName().addFamily(methodName);
IIdType id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); 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.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; 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.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.After;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
@ -89,10 +91,7 @@ import org.mockito.ArgumentCaptor;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.*;
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.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.entity.TagTypeEnum; import ca.uhn.fhir.jpa.entity.TagTypeEnum;
import ca.uhn.fhir.model.api.IQueryParameterType; 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); 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) { private void assertGone(IIdType theId) {
try { try {
assertNotGone(theId); assertNotGone(theId);
@ -152,7 +157,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
fail("Can't handle type: " + theId.getResourceType()); fail("Can't handle type: " + theId.getResourceType());
} }
} }
private List<String> extractNames(IBundleProvider theSearch) { private List<String> extractNames(IBundleProvider theSearch) {
ArrayList<String> retVal = new ArrayList<String>(); ArrayList<String> retVal = new ArrayList<String>();
for (IBaseResource next : theSearch.getResources(0, theSearch.size())) { for (IBaseResource next : theSearch.getResources(0, theSearch.size())) {
@ -167,7 +172,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
retVal.addCoding().setSystem(theSystem).setCode(theCode); retVal.addCoding().setSystem(theSystem).setCode(theCode);
return retVal; return retVal;
} }
private void sort(ArrayList<Coding> thePublished) { private void sort(ArrayList<Coding> thePublished) {
ArrayList<Coding> tags = new ArrayList<Coding>(thePublished); ArrayList<Coding> tags = new ArrayList<Coding>(thePublished);
Collections.sort(tags, new Comparator<Coding>() { Collections.sort(tags, new Comparator<Coding>() {
@ -194,7 +199,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
} }
}); });
} }
private List<UriType> sortIds(List<UriType> theProfiles) { private List<UriType> sortIds(List<UriType> theProfiles) {
ArrayList<UriType> retVal = new ArrayList<UriType>(theProfiles); ArrayList<UriType> retVal = new ArrayList<UriType>(theProfiles);
Collections.sort(retVal, new Comparator<UriType>() { Collections.sort(retVal, new Comparator<UriType>() {
@ -288,21 +293,21 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
Encounter enc = new Encounter(); Encounter enc = new Encounter();
enc.getPeriod().setStartElement(new DateTimeType("2016-05-10")).setEndElement(new DateTimeType("2016-05-20")); enc.getPeriod().setStartElement(new DateTimeType("2016-05-10")).setEndElement(new DateTimeType("2016-05-20"));
String id = myEncounterDao.create(enc, mySrd).getId().toUnqualifiedVersionless().getValue(); String id = myEncounterDao.create(enc, mySrd).getId().toUnqualifiedVersionless().getValue();
List<String> ids; List<String> ids;
/* /*
* This should not match, per the definition of eq * This should not match, per the definition of eq
*/ */
ids = toUnqualifiedVersionlessIdValues(myEncounterDao.search(Encounter.SP_DATE, new DateParam("2016-05-15"))); ids = toUnqualifiedVersionlessIdValues(myEncounterDao.search(Encounter.SP_DATE, new DateParam("2016-05-15")));
assertThat(ids, empty()); assertThat(ids, empty());
ids = toUnqualifiedVersionlessIdValues(myEncounterDao.search(Encounter.SP_DATE, new DateParam("eq2016-05-15"))); ids = toUnqualifiedVersionlessIdValues(myEncounterDao.search(Encounter.SP_DATE, new DateParam("eq2016-05-15")));
assertThat(ids, empty()); assertThat(ids, empty());
// Should match // Should match
ids = toUnqualifiedVersionlessIdValues(myEncounterDao.search(Encounter.SP_DATE, new DateParam("eq2016"))); ids = toUnqualifiedVersionlessIdValues(myEncounterDao.search(Encounter.SP_DATE, new DateParam("eq2016")));
assertThat(ids, contains(id)); assertThat(ids, contains(id));
@ -468,9 +473,9 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
CodeSystem cs = new CodeSystem(); CodeSystem cs = new CodeSystem();
cs.setStatus(PublicationStatus.DRAFT); cs.setStatus(PublicationStatus.DRAFT);
IIdType id = myCodeSystemDao.create(cs, mySrd).getId().toUnqualifiedVersionless(); IIdType id = myCodeSystemDao.create(cs, mySrd).getId().toUnqualifiedVersionless();
myCodeSystemDao.delete(id, mySrd); myCodeSystemDao.delete(id, mySrd);
assertGone(id.toUnqualifiedVersionless()); assertGone(id.toUnqualifiedVersionless());
} }
@ -544,7 +549,6 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
} }
@Test @Test
public void testCreateDifferentTypesWithSameForcedId() { public void testCreateDifferentTypesWithSameForcedId() {
String idName = "forcedId"; String idName = "forcedId";
@ -1047,7 +1051,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
@Test @Test
public void testDeleteResource() { public void testDeleteResource() {
int initialHistory = myPatientDao.history((Date)null, null, mySrd).size(); int initialHistory = myPatientDao.history((Date) null, null, mySrd).size();
IIdType id1; IIdType id1;
IIdType id2; IIdType id2;
@ -1089,7 +1093,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
// good // good
} }
IBundleProvider history = myPatientDao.history((Date)null, null, mySrd); IBundleProvider history = myPatientDao.history((Date) null, null, mySrd);
assertEquals(4 + initialHistory, history.size()); assertEquals(4 + initialHistory, history.size());
List<IBaseResource> resources = history.getResources(0, 4); List<IBaseResource> resources = history.getResources(0, 4);
assertNotNull(ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) resources.get(0))); assertNotNull(ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) resources.get(0)));
@ -1146,7 +1150,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
Observation obs1 = new Observation(); Observation obs1 = new Observation();
obs1.setStatus(ObservationStatus.FINAL); obs1.setStatus(ObservationStatus.FINAL);
IIdType obs1id = myObservationDao.create(obs1).getId().toUnqualifiedVersionless(); IIdType obs1id = myObservationDao.create(obs1).getId().toUnqualifiedVersionless();
Observation obs2 = new Observation(); Observation obs2 = new Observation();
obs2.setStatus(ObservationStatus.FINAL); obs2.setStatus(ObservationStatus.FINAL);
IIdType obs2id = myObservationDao.create(obs2).getId().toUnqualifiedVersionless(); IIdType obs2id = myObservationDao.create(obs2).getId().toUnqualifiedVersionless();
@ -1155,19 +1159,19 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
rpt.addIdentifier().setSystem("foo").setValue("IDENTIFIER"); rpt.addIdentifier().setSystem("foo").setValue("IDENTIFIER");
rpt.addResult(new Reference(obs2id)); rpt.addResult(new Reference(obs2id));
myDiagnosticReportDao.create(rpt).getId().toUnqualifiedVersionless(); myDiagnosticReportDao.create(rpt).getId().toUnqualifiedVersionless();
myObservationDao.read(obs1id); myObservationDao.read(obs1id);
myObservationDao.read(obs2id); myObservationDao.read(obs2id);
try { try {
myObservationDao.deleteByUrl("Observation?_has:DiagnosticReport:result:identifier=foo|IDENTIFIER", mySrd); myObservationDao.deleteByUrl("Observation?_has:DiagnosticReport:result:identifier=foo|IDENTIFIER", mySrd);
fail(); fail();
} catch (ResourceVersionConflictException e) { } catch (ResourceVersionConflictException e) {
assertConflictException(e); assertConflictException(e);
} }
myObservationDao.read(obs1id); myObservationDao.read(obs1id);
myObservationDao.read(obs2id); myObservationDao.read(obs2id);
} }
@Test @Test
@ -1461,7 +1465,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
} }
// By type // By type
history = myPatientDao.history((Date)null, null, mySrd); history = myPatientDao.history((Date) null, null, mySrd);
assertEquals(fullSize + 1, history.size()); assertEquals(fullSize + 1, history.size());
for (int i = 0; i < fullSize; i++) { for (int i = 0; i < fullSize; i++) {
String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue(); String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue();
@ -1528,7 +1532,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
} }
// By type // By type
history = myPatientDao.history((Date)null, null, mySrd); history = myPatientDao.history((Date) null, null, mySrd);
assertEquals(fullSize + 1, history.size()); assertEquals(fullSize + 1, history.size());
for (int i = 0; i < fullSize; i++) { for (int i = 0; i < fullSize; i++) {
String expected = id.withVersion(Integer.toString(fullSize + 1 - i)).getValue(); 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"); inPatient.getMeta().addProfile("http://example.com/1");
IIdType id = myPatientDao.create(inPatient, mySrd).getId().toUnqualifiedVersionless(); 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()); assertEquals(1, history.size());
Patient outPatient = (Patient) history.getResources(0, 1).get(0); Patient outPatient = (Patient) history.getResources(0, 1).get(0);
assertEquals("version1", inPatient.getName().get(0).getFamily()); assertEquals("version1", inPatient.getName().get(0).getFamily());
@ -1599,7 +1603,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
inPatient.getMeta().addProfile("http://example.com/2"); inPatient.getMeta().addProfile("http://example.com/2");
myPatientDao.metaAddOperation(id, inPatient.getMeta(), mySrd); myPatientDao.metaAddOperation(id, inPatient.getMeta(), mySrd);
history = myPatientDao.history((Date)null, null, mySrd); history = myPatientDao.history((Date) null, null, mySrd);
assertEquals(1, history.size()); assertEquals(1, history.size());
outPatient = (Patient) history.getResources(0, 1).get(0); outPatient = (Patient) history.getResources(0, 1).get(0);
assertEquals("version1", inPatient.getName().get(0).getFamily()); assertEquals("version1", inPatient.getName().get(0).getFamily());
@ -1615,7 +1619,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
inPatient.getName().get(0).setFamily("version2"); inPatient.getName().get(0).setFamily("version2");
myPatientDao.update(inPatient, mySrd); myPatientDao.update(inPatient, mySrd);
history = myPatientDao.history((Date)null, null, mySrd); history = myPatientDao.history((Date) null, null, mySrd);
assertEquals(2, history.size()); assertEquals(2, history.size());
outPatient = (Patient) history.getResources(0, 2).get(0); outPatient = (Patient) history.getResources(0, 2).get(0);
assertEquals("version2", outPatient.getName().get(0).getFamily()); assertEquals("version2", outPatient.getName().get(0).getFamily());
@ -1682,13 +1686,13 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
} }
List<String> idValues; List<String> idValues;
idValues = toUnqualifiedIdValues(myPatientDao.history(id, preDates.get(0), preDates.get(3), mySrd)); idValues = toUnqualifiedIdValues(myPatientDao.history(id, preDates.get(0), preDates.get(3), mySrd));
assertThat(idValues, contains(ids.get(2), ids.get(1), ids.get(0))); assertThat(idValues, contains(ids.get(2), ids.get(1), ids.get(0)));
idValues = toUnqualifiedIdValues(myPatientDao.history(preDates.get(0), preDates.get(3), mySrd)); idValues = toUnqualifiedIdValues(myPatientDao.history(preDates.get(0), preDates.get(3), mySrd));
assertThat(idValues, contains(ids.get(2), ids.get(1), ids.get(0))); assertThat(idValues, contains(ids.get(2), ids.get(1), ids.get(0)));
idValues = toUnqualifiedIdValues(mySystemDao.history(preDates.get(0), preDates.get(3), mySrd)); idValues = toUnqualifiedIdValues(mySystemDao.history(preDates.get(0), preDates.get(3), mySrd));
assertThat(idValues, contains(ids.get(2), ids.get(1), ids.get(0))); assertThat(idValues, contains(ids.get(2), ids.get(1), ids.get(0)));
} }
@ -1709,7 +1713,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
// No since // No since
IBundleProvider history = myPatientDao.history((Date)null, null, mySrd); IBundleProvider history = myPatientDao.history((Date) null, null, mySrd);
assertEquals(1, history.size()); assertEquals(1, history.size());
Patient outPatient = (Patient) history.getResources(0, 1).get(0); Patient outPatient = (Patient) history.getResources(0, 1).get(0);
assertEquals("version1", inPatient.getName().get(0).getFamily()); assertEquals("version1", inPatient.getName().get(0).getFamily());
@ -1731,7 +1735,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
assertEquals(0, history.size()); assertEquals(0, history.size());
} }
@Test @Test
public void testHistoryWithInvalidId() throws Exception { public void testHistoryWithInvalidId() throws Exception {
try { try {
@ -1815,11 +1819,11 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
@Test @Test
public void testIndexConditionWithAllOnsetTypes() { public void testIndexConditionWithAllOnsetTypes() {
// DateTimeType.class, Age.class, Period.class, Range.class, StringType.class // DateTimeType.class, Age.class, Period.class, Range.class, StringType.class
Condition c0 = new Condition(); Condition c0 = new Condition();
c0.setOnset(new DateTimeType("2011-01-01")); c0.setOnset(new DateTimeType("2011-01-01"));
myConditionDao.create(c0, mySrd).getId().toUnqualifiedVersionless(); myConditionDao.create(c0, mySrd).getId().toUnqualifiedVersionless();
Condition c1 = new Condition(); Condition c1 = new Condition();
c1.setOnset(new Age().setValue(100L).setCode("AGECODE")); c1.setOnset(new Age().setValue(100L).setCode("AGECODE"));
myConditionDao.create(c1, mySrd).getId().toUnqualifiedVersionless(); myConditionDao.create(c1, mySrd).getId().toUnqualifiedVersionless();
@ -1835,7 +1839,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
Condition c4 = new Condition(); Condition c4 = new Condition();
c4.setOnset(new StringType("FOO")); c4.setOnset(new StringType("FOO"));
myConditionDao.create(c4, mySrd).getId().toUnqualifiedVersionless(); myConditionDao.create(c4, mySrd).getId().toUnqualifiedVersionless();
} }
@Test @Test
public void testInstanceMetaOperations() { 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 @Test
public void testOrganizationName() { public void testOrganizationName() {
@ -3297,7 +3322,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
Date before = new DateTimeType("2011-01-01T10:00:00Z").getValue(); Date before = new DateTimeType("2011-01-01T10:00:00Z").getValue();
Date middle = new DateTimeType("2011-01-02T10:00:00Z").getValue(); Date middle = new DateTimeType("2011-01-02T10:00:00Z").getValue();
Date after = new DateTimeType("2011-01-03T10:00:00Z").getValue(); Date after = new DateTimeType("2011-01-03T10:00:00Z").getValue();
CarePlan cp = new CarePlan(); CarePlan cp = new CarePlan();
cp.addActivity().getDetail().setScheduled(new Timing().addEvent(before).addEvent(middle).addEvent(after)); cp.addActivity().getDetail().setScheduled(new Timing().addEvent(before).addEvent(middle).addEvent(after));
cp.addActivity().getDetail(); cp.addActivity().getDetail();
@ -3307,9 +3332,9 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
cp2.addActivity().getDetail().setScheduled(new StringType("FOO")); cp2.addActivity().getDetail().setScheduled(new StringType("FOO"));
cp2.addActivity().getDetail(); cp2.addActivity().getDetail();
myCarePlanDao.create(cp2, mySrd).getId().toUnqualifiedVersionless(); myCarePlanDao.create(cp2, mySrd).getId().toUnqualifiedVersionless();
SearchParameterMap params; SearchParameterMap params;
params = new SearchParameterMap(); params = new SearchParameterMap();
params.add(CarePlan.SP_ACTIVITY_DATE, new DateRangeParam("2010-01-01T10:00:00Z", null)); params.add(CarePlan.SP_ACTIVITY_DATE, new DateRangeParam("2010-01-01T10:00:00Z", null));
assertThat(toUnqualifiedVersionlessIdValues(myCarePlanDao.search(params)), contains(id.getValue())); assertThat(toUnqualifiedVersionlessIdValues(myCarePlanDao.search(params)), contains(id.getValue()));
@ -3317,7 +3342,7 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
params = new SearchParameterMap(); params = new SearchParameterMap();
params.add(CarePlan.SP_ACTIVITY_DATE, new DateRangeParam("2011-01-01T10:00:00Z", null)); params.add(CarePlan.SP_ACTIVITY_DATE, new DateRangeParam("2011-01-01T10:00:00Z", null));
assertThat(toUnqualifiedVersionlessIdValues(myCarePlanDao.search(params)), contains(id.getValue())); assertThat(toUnqualifiedVersionlessIdValues(myCarePlanDao.search(params)), contains(id.getValue()));
params = new SearchParameterMap(); params = new SearchParameterMap();
params.add(CarePlan.SP_ACTIVITY_DATE, new DateRangeParam("2012-01-01T10:00:00Z", null)); params.add(CarePlan.SP_ACTIVITY_DATE, new DateRangeParam("2012-01-01T10:00:00Z", null));
assertThat(toUnqualifiedVersionlessIdValues(myCarePlanDao.search(params)), empty()); assertThat(toUnqualifiedVersionlessIdValues(myCarePlanDao.search(params)), empty());
@ -3364,7 +3389,8 @@ public class FhirResourceDaoDstu3Test extends BaseJpaDstu3Test {
} }
public static void assertConflictException(ResourceVersionConflictException e) { 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) { 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 { protected void initialize() throws ServletException {
super.initialize(); super.initialize();
/* /*
* We want to support FHIR DSTU2 format. This means that the server * We want to support FHIR DSTU2 format. This means that the server
* will use the DSTU2 bundle format and other DSTU2 encoding changes. * will use the DSTU2 bundle format and other DSTU2 encoding changes.
* *
* If you want to use DSTU1 instead, change the following line, and * If you want to use DSTU1 instead, change the following line, and
* change the 2 occurrences of dstu2 in web.xml to dstu1 * change the 2 occurrences of dstu2 in web.xml to dstu1
*/ */
FhirVersionEnum fhirVersion = FhirVersionEnum.DSTU2; FhirVersionEnum fhirVersion = FhirVersionEnum.DSTU3;
FhirContext context = new FhirContext(fhirVersion); setFhirContext(new FhirContext(fhirVersion));
setFhirContext(context);
// Get the spring context from the web container (it's declared in web.xml) // Get the spring context from the web container (it's declared in web.xml)
myAppCtx = ContextLoaderListener.getCurrentWebApplicationContext(); myAppCtx = ContextLoaderListener.getCurrentWebApplicationContext();
/* /*
* The BaseJavaConfigDstu2.java class is a spring configuration * The BaseJavaConfigDstu2.java class is a spring configuration
* file which is automatically generated as a part of hapi-fhir-jpaserver-base and * 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 * 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); List<IResourceProvider> beans = myAppCtx.getBean(resourceProviderBeanName, List.class);
setResourceProviders(beans); setResourceProviders(beans);
/* /*
* The system provider implements non-resource-type methods, such as * The system provider implements non-resource-type methods, such as
* transaction, and global history. * 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 * 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 * 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: * just refer to "localhost", you might want to use a server address strategy:
*/ */
//setServerAddressStrategy(new HardcodedServerAddressStrategy("http://mydomain.com/fhir/baseDstu2")); //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 * 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) * 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 * so it is a potential security vulnerability. Consider using an AuthorizationInterceptor

View File

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

View File

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

View File

@ -1,10 +1,14 @@
package ca.uhn.fhirtest.config; package ca.uhn.fhirtest.config;
import java.util.Properties; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import javax.persistence.EntityManagerFactory; import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
import javax.sql.DataSource; 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.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.dialect.DerbyTenSevenDialect; 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.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.EnableTransactionManagement;
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; import javax.persistence.EntityManagerFactory;
import ca.uhn.fhir.jpa.dao.DaoConfig; import javax.sql.DataSource;
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2; import java.util.Properties;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhirtest.interceptor.TdlSecurityInterceptor;
@Configuration @Configuration
@Import(CommonConfig.class) @Import(CommonConfig.class)
@ -113,6 +115,49 @@ public class TdlDstu2Config extends BaseJavaConfigDstu2 {
return extraProperties; 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) @Bean(autowire=Autowire.BY_TYPE)
public IServerInterceptor subscriptionSecurityInterceptor() { public IServerInterceptor subscriptionSecurityInterceptor() {
return new SubscriptionsRequireManualActivationInterceptorDstu2(); return new SubscriptionsRequireManualActivationInterceptorDstu2();

View File

@ -1,30 +1,28 @@
package ca.uhn.fhirtest.config; package ca.uhn.fhirtest.config;
import java.util.Properties; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import javax.persistence.EntityManagerFactory; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import javax.sql.DataSource; 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.dbcp2.BasicDataSource;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.hibernate.dialect.PostgreSQL94Dialect; import org.hibernate.dialect.PostgreSQL94Dialect;
import org.hibernate.jpa.HibernatePersistenceProvider; import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.EnableTransactionManagement;
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; import javax.persistence.EntityManagerFactory;
import ca.uhn.fhir.jpa.dao.DaoConfig; import javax.sql.DataSource;
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2; import java.util.Properties;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhirtest.interceptor.PublicSecurityInterceptor;
@Configuration @Configuration
@Import(CommonConfig.class) @Import(CommonConfig.class)
@ -114,6 +112,22 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
return extraProperties; 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) // @Bean(autowire = Autowire.BY_TYPE)
// public IServerInterceptor subscriptionSecurityInterceptor() { // public IServerInterceptor subscriptionSecurityInterceptor() {
// return new SubscriptionsRequireManualActivationInterceptorDstu2(); // return new SubscriptionsRequireManualActivationInterceptorDstu2();

View File

@ -26,6 +26,27 @@
Web testing UI displayed an error when a transaction was pasted into the UI Web testing UI displayed an error when a transaction was pasted into the UI
for a DSTU2 server. Thanks to Suresh Kumar for reporting! for a DSTU2 server. Thanks to Suresh Kumar for reporting!
</action> </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>
<release version="2.3" date="2017-03-18"> <release version="2.3" date="2017-03-18">
<action type="add"> <action type="add">

View File

@ -174,6 +174,38 @@ public DaoConfig daoConfig() {
return retVal; return retVal;
}]]></source> }]]></source>
</subsection> </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> </section>