diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/MarkdownDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/MarkdownDt.java
index 0190d989b46..05254e810d4 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/MarkdownDt.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/MarkdownDt.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.model.primitive;
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2015 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 ca.uhn.fhir.model.api.annotation.DatatypeDef;
import net.sourceforge.cobertura.CoverageIgnore;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OperationParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OperationParam.java
index 82b07d87a5e..ed204bbd8ce 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OperationParam.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/OperationParam.java
@@ -59,16 +59,16 @@ public @interface OperationParam {
Class extends IBase> type() default IBase.class;
/**
- * The minimum number of repetitions allowed for this child
+ * The minimum number of repetitions allowed for this child (default is 0)
*/
int min() default 0;
/**
* The maximum number of repetitions allowed for this child. Should be
* set to {@link #MAX_UNLIMITED} if there is no limit to the number of
- * repetitions.
+ * repetitions (default is 1)
*/
- int max() default MAX_UNLIMITED;
+ int max() default 1;
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/BearerTokenAuthInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/BearerTokenAuthInterceptor.java
index e915be918b1..ccf0a73f473 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/BearerTokenAuthInterceptor.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/BearerTokenAuthInterceptor.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.client.interceptor;
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2015 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 org.apache.commons.lang3.Validate;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml
index 817730b0d4b..dea3993dd2c 100644
--- a/hapi-fhir-jpaserver-base/pom.xml
+++ b/hapi-fhir-jpaserver-base/pom.xml
@@ -174,6 +174,10 @@
org.springframework
spring-context-support
+
+ org.springframework
+ spring-test
+
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java
index 65b159e2646..0f31e6412af 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java
@@ -19,8 +19,8 @@ package ca.uhn.fhir.jpa.dao;
* limitations under the License.
* #L%
*/
-
-import static org.apache.commons.lang3.StringUtils.*;
+import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.UnsupportedEncodingException;
import java.net.URI;
@@ -115,7 +115,7 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.FhirTerser;
-public abstract class BaseHapiFhirDao implements IDao {
+public abstract class BaseHapiFhirDao implements IDao {
public static final String NS_JPA_PROFILE = "https://github.com/jamesagnew/hapi-fhir/ns/jpa/profile";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirDao.class);
@@ -344,7 +344,7 @@ public abstract class BaseHapiFhirDao implements IDao {
}
@SuppressWarnings("unchecked")
- protected IFhirResourceDao getDao(Class theType) {
+ protected IFhirResourceDao getDao(Class theType) {
if (myResourceTypeToDao == null) {
myResourceTypeToDao = new HashMap, IFhirResourceDao>>();
for (IFhirResourceDao> next : myResourceDaos) {
@@ -358,7 +358,7 @@ public abstract class BaseHapiFhirDao implements IDao {
}
- return (IFhirResourceDao) myResourceTypeToDao.get(theType);
+ return (IFhirResourceDao) myResourceTypeToDao.get(theType);
}
protected TagDefinition getTag(TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
@@ -644,12 +644,12 @@ public abstract class BaseHapiFhirDao implements IDao {
}
- protected Set processMatchUrl(String theMatchUrl, Class theResourceType) {
+ protected Set processMatchUrl(String theMatchUrl, Class theResourceType) {
RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(theResourceType);
SearchParameterMap paramMap = translateMatchUrl(theMatchUrl, resourceDef);
- IFhirResourceDao dao = getDao(theResourceType);
+ IFhirResourceDao dao = getDao(theResourceType);
Set ids = dao.searchForIdsWithAndOr(paramMap);
return ids;
@@ -899,6 +899,7 @@ public abstract class BaseHapiFhirDao implements IDao {
case DSTU1:
mySearchParamExtractor = new SearchParamExtractorDstu1(theContext);
break;
+ case DSTU2_HL7ORG:
case DEV:
throw new IllegalStateException("Don't know how to handle version: " + myContext.getVersion().getVersion());
}
@@ -917,7 +918,7 @@ public abstract class BaseHapiFhirDao implements IDao {
return toResource(type.getImplementingClass(), theEntity);
}
- protected T toResource(Class theResourceType, BaseHasResource theEntity) {
+ protected R toResource(Class theResourceType, BaseHasResource theEntity) {
String resourceText = null;
switch (theEntity.getEncoding()) {
case JSON:
@@ -933,7 +934,7 @@ public abstract class BaseHapiFhirDao implements IDao {
}
IParser parser = theEntity.getEncoding().newParser(getContext(theEntity.getFhirVersion()));
- T retVal;
+ R retVal;
try {
retVal = parser.parseResource(theResourceType, resourceText);
} catch (Exception e) {
@@ -955,9 +956,9 @@ public abstract class BaseHapiFhirDao implements IDao {
IResource res = (IResource) retVal;
res.setId(theEntity.getIdDt());
- res.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, theEntity.getVersion());
- res.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, theEntity.getPublished());
- res.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, theEntity.getUpdated());
+ ResourceMetadataKeyEnum.VERSION.put(res, Long.toString(theEntity.getVersion()));
+ ResourceMetadataKeyEnum.PUBLISHED.put(res, theEntity.getPublished());
+ ResourceMetadataKeyEnum.UPDATED.put(res, theEntity.getUpdated());
if (theEntity.getTitle() != null) {
ResourceMetadataKeyEnum.TITLE.put(res, theEntity.getTitle());
@@ -1038,9 +1039,10 @@ public abstract class BaseHapiFhirDao implements IDao {
return updateEntity(theResource, entity, theUpdateHistory, theDeletedTimestampOrNull, true, true);
}
+ @SuppressWarnings("unchecked")
protected ResourceTable updateEntity(final IResource theResource, ResourceTable entity, boolean theUpdateHistory, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion) {
- validateResourceForStorage(theResource);
+ validateResourceForStorage((T) theResource);
if (entity.getPublished() == null) {
entity.setPublished(new Date());
@@ -1216,12 +1218,15 @@ public abstract class BaseHapiFhirDao implements IDao {
}
/**
- * Subclasses may override to provide specific behaviour
+ * This method is invoked immediately before storing a new resource, or an
+ * update to an existing resource to allow the DAO to ensure that it is valid
+ * for persistence. By default, no validation is performed, but subclasses
+ * may override to provide specific behaviour.
*
* @param theResource
* The resource that is about to be persisted
*/
- protected void validateResourceForStorage(IResource theResource) {
+ protected void validateResourceForStorage(T theResource) {
// nothing
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
index b865951fe6a..fa16591eec6 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
@@ -131,7 +131,7 @@ import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.ObjectUtil;
@Transactional(propagation = Propagation.REQUIRED)
-public abstract class BaseHapiFhirResourceDao extends BaseHapiFhirDao implements IFhirResourceDao {
+public abstract class BaseHapiFhirResourceDao extends BaseHapiFhirDao implements IFhirResourceDao {
static final String OO_SEVERITY_WARN = "warning";
static final String OO_SEVERITY_INFO = "information";
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoQuestionnaireResponseDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoQuestionnaireResponseDstu2.java
index 4ceacc36740..0daa1bc776c 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoQuestionnaireResponseDstu2.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoQuestionnaireResponseDstu2.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.dao;
+/*
+ * #%L
+ * HAPI FHIR JPA Server
+ * %%
+ * Copyright (C) 2014 - 2015 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 javax.annotation.PostConstruct;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
@@ -7,7 +27,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.Questionnaire;
import ca.uhn.fhir.model.dstu2.resource.QuestionnaireResponse;
@@ -39,7 +58,7 @@ public class FhirResourceDaoQuestionnaireResponseDstu2 extends FhirResourceDaoDs
}
@Override
- protected void validateResourceForStorage(IResource theResource) {
+ protected void validateResourceForStorage(QuestionnaireResponse theResource) {
super.validateResourceForStorage(theResource);
if (!myValidateResponses) {
return;
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java
new file mode 100644
index 00000000000..5f292b1710d
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2.java
@@ -0,0 +1,187 @@
+package ca.uhn.fhir.jpa.dao;
+
+import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.codec.binary.StringUtils;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.instance.model.api.IPrimitiveType;
+
+import ca.uhn.fhir.jpa.entity.BaseHasResource;
+import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
+import ca.uhn.fhir.model.dstu2.composite.CodingDt;
+import ca.uhn.fhir.model.dstu2.resource.ValueSet;
+import ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept;
+import ca.uhn.fhir.model.dstu2.resource.ValueSet.ComposeInclude;
+import ca.uhn.fhir.model.dstu2.resource.ValueSet.ComposeIncludeConcept;
+import ca.uhn.fhir.model.dstu2.resource.ValueSet.ExpansionContains;
+import ca.uhn.fhir.model.primitive.CodeDt;
+import ca.uhn.fhir.model.primitive.DateTimeDt;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.model.primitive.StringDt;
+import ca.uhn.fhir.model.primitive.UriDt;
+import ca.uhn.fhir.rest.param.TokenParam;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
+
+public class FhirResourceDaoValueSetDstu2 extends FhirResourceDaoDstu2implements IFhirResourceDaoValueSet {
+
+ @Override
+ public ValueSet expand(IIdType theId, StringDt theFilter) {
+ ValueSet retVal = new ValueSet();
+ retVal.setDate(DateTimeDt.withCurrentTime());
+
+ BaseHasResource sourceEntity = readEntity(theId);
+ if (sourceEntity == null) {
+ throw new ResourceNotFoundException(theId);
+ }
+ ValueSet source = (ValueSet) toResource(sourceEntity);
+
+ /*
+ * Add composed concepts
+ */
+
+ for (ComposeInclude nextInclude : source.getCompose().getInclude()) {
+ for (ComposeIncludeConcept next : nextInclude.getConcept()) {
+ if (theFilter == null || theFilter.isEmpty()) {
+ addCompose(retVal, nextInclude.getSystem(), next.getCode(), next.getDisplay());
+ } else {
+ String filter = theFilter.getValue().toLowerCase();
+ if (next.getDisplay().toLowerCase().contains(filter) || next.getCode().toLowerCase().contains(filter)) {
+ addCompose(retVal, nextInclude.getSystem(), next.getCode(), next.getDisplay());
+ }
+ }
+ }
+ }
+
+ /*
+ * Add defined concepts
+ */
+
+ for (CodeSystemConcept next : source.getCodeSystem().getConcept()) {
+ addCompose(theFilter, retVal, source, next);
+ }
+
+ return retVal;
+
+ }
+
+ private void addCompose(StringDt theFilter, ValueSet theValueSetToPopulate, ValueSet theSourceValueSet, CodeSystemConcept theConcept) {
+ if (theFilter == null || theFilter.isEmpty()) {
+ addCompose(theValueSetToPopulate, theSourceValueSet.getCodeSystem().getSystem(), theConcept.getCode(), theConcept.getDisplay());
+ } else {
+ String filter = theFilter.getValue().toLowerCase();
+ if (theConcept.getDisplay().toLowerCase().contains(filter) || theConcept.getCode().toLowerCase().contains(filter)) {
+ addCompose(theValueSetToPopulate, theSourceValueSet.getCodeSystem().getSystem(), theConcept.getCode(), theConcept.getDisplay());
+ }
+ }
+ for (CodeSystemConcept nextChild : theConcept.getConcept()) {
+ addCompose(theFilter, theValueSetToPopulate, theSourceValueSet, nextChild);
+ }
+ }
+
+ private void addCompose(ValueSet retVal, String theSystem, String theCode, String theDisplay) {
+ if (isBlank(theCode)) {
+ return;
+ }
+ ExpansionContains contains = retVal.getExpansion().addContains();
+ contains.setSystem(theSystem);
+ contains.setCode(theCode);
+ contains.setDisplay(theDisplay);
+ }
+
+ @Override
+ public ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult validateCode(UriDt theValueSetIdentifier, IIdType theId, CodeDt theCode, UriDt theSystem, StringDt theDisplay,
+ CodingDt theCoding, CodeableConceptDt theCodeableConcept) {
+ List valueSetIds;
+
+ boolean haveCodeableConcept = theCodeableConcept != null && theCodeableConcept.getCoding().size() > 0;
+ boolean haveCoding = theCoding != null && theCoding.isEmpty() == false;
+ boolean haveCode = theCode != null && theCode.isEmpty() == false;
+
+ if (!haveCodeableConcept && !haveCoding && !haveCode) {
+ throw new InvalidRequestException("No code, coding, or codeableConcept provided to validate");
+ }
+ if (!(haveCodeableConcept ^ haveCoding ^ haveCode)) {
+ throw new InvalidRequestException("$validate-code can only validate (system AND code) OR (coding) OR (codeableConcept)");
+ }
+
+ boolean haveIdentifierParam = theValueSetIdentifier != null && theValueSetIdentifier.isEmpty() == false;
+ if (theId != null) {
+ valueSetIds = Collections.singletonList((IIdType) theId);
+ } else if (haveIdentifierParam) {
+ Set ids = searchForIds(ValueSet.SP_IDENTIFIER, new TokenParam(null, theValueSetIdentifier.getValue()));
+ valueSetIds = new ArrayList();
+ for (Long next : ids) {
+ valueSetIds.add(new IdDt("ValueSet", next));
+ }
+ } else {
+ if (theCode == null || theCode.isEmpty()) {
+ throw new InvalidRequestException("Either ValueSet ID or ValueSet identifier or system and code must be provided. Unable to validate.");
+ }
+ Set ids = searchForIds(ValueSet.SP_CODE, new TokenParam(toStringOrNull(theSystem), theCode.getValue()));
+ valueSetIds = new ArrayList();
+ for (Long next : ids) {
+ valueSetIds.add(new IdDt("ValueSet", next));
+ }
+ }
+
+ for (IIdType nextId : valueSetIds) {
+ ValueSet expansion = expand(nextId, null);
+ List contains = expansion.getExpansion().getContains();
+ ValidateCodeResult result = validateCodeIsInContains(contains, toStringOrNull(theSystem), toStringOrNull(theCode), theCoding, theCodeableConcept);
+ if (result != null) {
+ if (theDisplay != null && isNotBlank(theDisplay.getValue()) && isNotBlank(result.getDisplay())) {
+ if (!theDisplay.getValue().equals(result.getDisplay())) {
+ return new ValidateCodeResult(false, "Display for code does not match", result.getDisplay());
+ }
+ }
+ return result;
+ }
+ }
+
+ return new ValidateCodeResult(false, "Code not found", null);
+ }
+
+ private String toStringOrNull(IPrimitiveType thePrimitive) {
+ return thePrimitive != null ? thePrimitive.getValue() : null;
+ }
+
+ private ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult validateCodeIsInContains(List contains, String theSystem, String theCode, CodingDt theCoding,
+ CodeableConceptDt theCodeableConcept) {
+ for (ExpansionContains nextCode : contains) {
+ ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult result = validateCodeIsInContains(nextCode.getContains(), theSystem, theCode, theCoding, theCodeableConcept);
+ if (result != null) {
+ return result;
+ }
+
+ String system = nextCode.getSystem();
+ String code = nextCode.getCode();
+
+ if (isNotBlank(theCode)) {
+ if (theCode.equals(code) && (isBlank(theSystem) || theSystem.equals(system))) {
+ return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
+ }
+ } else if (theCoding != null) {
+ if (StringUtils.equals(system, theCoding.getSystem()) && StringUtils.equals(code, theCoding.getCode())) {
+ return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
+ }
+ } else {
+ for (CodingDt next : theCodeableConcept.getCoding()) {
+ if (StringUtils.equals(system, next.getSystem()) && StringUtils.equals(code, next.getCode())) {
+ return new ValidateCodeResult(true, "Validation succeeded", nextCode.getDisplay());
+ }
+ }
+ }
+
+ }
+
+ return null;
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoValueSet.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoValueSet.java
new file mode 100644
index 00000000000..d415c8ef845
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoValueSet.java
@@ -0,0 +1,44 @@
+package ca.uhn.fhir.jpa.dao;
+
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+
+import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
+import ca.uhn.fhir.model.dstu2.composite.CodingDt;
+import ca.uhn.fhir.model.dstu2.resource.ValueSet;
+import ca.uhn.fhir.model.primitive.CodeDt;
+import ca.uhn.fhir.model.primitive.StringDt;
+import ca.uhn.fhir.model.primitive.UriDt;
+
+public interface IFhirResourceDaoValueSet extends IFhirResourceDao {
+
+ ValueSet expand(IIdType theId, StringDt theFilter);
+
+ ValidateCodeResult validateCode(UriDt theValueSetIdentifier, IIdType theId, CodeDt theCode, UriDt theSystem, StringDt theDisplay, CodingDt theCoding, CodeableConceptDt theCodeableConcept);
+
+ public class ValidateCodeResult {
+ private String myDisplay;
+ private String myMessage;
+ private boolean myResult;
+
+ public ValidateCodeResult(boolean theResult, String theMessage, String theDisplay) {
+ super();
+ myResult = theResult;
+ myMessage = theMessage;
+ myDisplay = theDisplay;
+ }
+
+ public String getDisplay() {
+ return myDisplay;
+ }
+
+ public String getMessage() {
+ return myMessage;
+ }
+
+ public boolean isResult() {
+ return myResult;
+ }
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu2.java
index 49863dea8b6..c7cfddfe237 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu2.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu2.java
@@ -63,6 +63,7 @@ import ca.uhn.fhir.model.dstu2.resource.Conformance.RestSecurity;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.resource.Patient.Communication;
import ca.uhn.fhir.model.dstu2.resource.Questionnaire;
+import ca.uhn.fhir.model.dstu2.resource.ValueSet;
import ca.uhn.fhir.model.dstu2.valueset.RestfulSecurityServiceEnum;
import ca.uhn.fhir.model.primitive.BaseDateTimeDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
@@ -405,6 +406,12 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
public List extractSearchParamTokens(ResourceTable theEntity, IResource theResource) {
ArrayList retVal = new ArrayList();
+ String useSystem = null;
+ if (theResource instanceof ValueSet) {
+ ValueSet vs = (ValueSet) theResource;
+ useSystem = vs.getCodeSystem().getSystem();
+ }
+
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource);
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.TOKEN) {
@@ -466,7 +473,11 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen
if (nextValue.isEmpty()) {
continue;
}
- systems.add(null);
+ if ("ValueSet.codeSystem.concept.code".equals(nextPath)) {
+ systems.add(useSystem);
+ } else {
+ systems.add(null);
+ }
codes.add(nextValue.getValueAsString());
} else if (nextObject instanceof CodingDt) {
CodingDt nextValue = (CodingDt) nextObject;
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderQuestionnaireResponseDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderQuestionnaireResponseDstu2.java
index 2c665f85ccb..effd5740a69 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderQuestionnaireResponseDstu2.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderQuestionnaireResponseDstu2.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.jpa.provider;
+/*
+ * #%L
+ * HAPI FHIR JPA Server
+ * %%
+ * Copyright (C) 2014 - 2015 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 ca.uhn.fhir.model.dstu2.resource.QuestionnaireResponse;
public class BaseJpaResourceProviderQuestionnaireResponseDstu2 extends JpaResourceProviderDstu2 {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderValueSetDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderValueSetDstu2.java
index f7626b10725..7123da8a607 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderValueSetDstu2.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/BaseJpaResourceProviderValueSetDstu2.java
@@ -1,116 +1,76 @@
package ca.uhn.fhir.jpa.provider;
-/*
- * #%L
- * HAPI FHIR JPA Server
- * %%
- * Copyright (C) 2014 - 2015 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.HashMap;
-import java.util.Map;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
import javax.servlet.http.HttpServletRequest;
+import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet;
+import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
+import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
+import ca.uhn.fhir.model.dstu2.composite.CodingDt;
+import ca.uhn.fhir.model.dstu2.resource.Parameters;
import ca.uhn.fhir.model.dstu2.resource.ValueSet;
-import ca.uhn.fhir.model.dstu2.resource.ValueSet.CodeSystemConcept;
-import ca.uhn.fhir.model.dstu2.resource.ValueSet.ComposeInclude;
-import ca.uhn.fhir.model.dstu2.resource.ValueSet.ComposeIncludeConcept;
-import ca.uhn.fhir.model.primitive.DateTimeDt;
+import ca.uhn.fhir.model.primitive.BooleanDt;
+import ca.uhn.fhir.model.primitive.CodeDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
+import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
public class BaseJpaResourceProviderValueSetDstu2 extends JpaResourceProviderDstu2 {
+ //@formatter:off
@Operation(name = "$expand", idempotent = true)
- public ValueSet everything(HttpServletRequest theServletRequest, @IdParam IdDt theId, @OperationParam(name = "filter") StringDt theFilter) {
+ public ValueSet everything(
+ HttpServletRequest theServletRequest,
+ @IdParam IdDt theId,
+ @OperationParam(name = "filter") StringDt theFilter) {
+ //@formatter:on
+
startRequest(theServletRequest);
try {
- ValueSet retVal = new ValueSet();
- retVal.setDate(DateTimeDt.withCurrentTime());
-
- ValueSet source = read(theServletRequest, theId);
-
- Map systemToCompose = new HashMap();
-
- /*
- * Add composed concepts
- */
-
- for (ComposeInclude nextInclude : source.getCompose().getInclude()) {
- for (ComposeIncludeConcept next : nextInclude.getConcept()) {
- ComposeInclude include = null;
- if (theFilter == null || theFilter.isEmpty()) {
- if (include == null) {
- include = getOrAddComposeInclude(retVal, systemToCompose, nextInclude.getSystem());
- }
- include.addConcept(next);
- } else {
- String filter = theFilter.getValue().toLowerCase();
- if (next.getDisplay().toLowerCase().contains(filter) || next.getCode().toLowerCase().contains(filter)) {
- if (include == null) {
- include = getOrAddComposeInclude(retVal, systemToCompose, nextInclude.getSystem());
- }
- include.addConcept(next);
- }
- }
- }
- }
-
- /*
- * Add defined concepts
- */
-
- ComposeInclude include = null;
- for (CodeSystemConcept next : source.getCodeSystem().getConcept()) {
- if (theFilter == null || theFilter.isEmpty()) {
- if (include == null) {
- include = getOrAddComposeInclude(retVal, systemToCompose, source.getCodeSystem().getSystem());
- }
- include.addConcept(new ComposeIncludeConcept().setCode(next.getCode()).setDisplay(next.getDisplay()));
- } else {
- String filter = theFilter.getValue().toLowerCase();
- if (next.getDisplay().toLowerCase().contains(filter) || next.getCode().toLowerCase().contains(filter)) {
- if (include == null) {
- include = getOrAddComposeInclude(retVal, systemToCompose, source.getCodeSystem().getSystem());
- }
- include.addConcept(new ComposeIncludeConcept().setCode(next.getCode()).setDisplay(next.getDisplay()));
- }
- }
- }
-
- return retVal;
-
+ IFhirResourceDaoValueSet dao = (IFhirResourceDaoValueSet) getDao();
+ return dao.expand(theId, theFilter);
} finally {
endRequest(theServletRequest);
}
}
- private ComposeInclude getOrAddComposeInclude(ValueSet retVal, Map systemToCompose, String system) {
- ComposeInclude include;
- include = systemToCompose.get(system);
- if (include == null) {
- include = retVal.getCompose().addInclude();
- include.setSystem(system);
- systemToCompose.put(system, include);
+ //@formatter:off
+ @Operation(name = "$validate-code", idempotent = true, returnParameters= {
+ @OperationParam(name="result", type=BooleanDt.class, min=1),
+ @OperationParam(name="message", type=StringDt.class),
+ @OperationParam(name="display", type=StringDt.class)
+ })
+ public Parameters everything(
+ HttpServletRequest theServletRequest,
+ @IdParam IdDt theId,
+ @OperationParam(name="identifier") UriDt theValueSetIdentifier,
+ @OperationParam(name="code") CodeDt theCode,
+ @OperationParam(name="system") UriDt theSystem,
+ @OperationParam(name="display") StringDt theDisplay,
+ @OperationParam(name="coding") CodingDt theCoding,
+ @OperationParam(name="codeableConcept") CodeableConceptDt theCodeableConcept
+ ) {
+ //@formatter:on
+
+ startRequest(theServletRequest);
+ try {
+ IFhirResourceDaoValueSet dao = (IFhirResourceDaoValueSet) getDao();
+ ValidateCodeResult result = dao.validateCode(theValueSetIdentifier, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept);
+ Parameters retVal = new Parameters();
+ retVal.addParameter().setName("result").setValue(new BooleanDt(result.isResult()));
+ if (isNotBlank(result.getMessage())) {
+ retVal.addParameter().setName("message").setValue(new StringDt(result.getMessage()));
+ }
+ if (isNotBlank(result.getDisplay())) {
+ retVal.addParameter().setName("display").setValue(new StringDt(result.getDisplay()));
+ }
+ return retVal;
+ } finally {
+ endRequest(theServletRequest);
}
- return include;
}
-
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2Test.java
new file mode 100644
index 00000000000..b21849ce836
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoValueSetDstu2Test.java
@@ -0,0 +1,217 @@
+package ca.uhn.fhir.jpa.dao;
+
+import static org.hamcrest.Matchers.stringContainsInOrder;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.transaction.annotation.Transactional;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
+import ca.uhn.fhir.jpa.provider.ResourceProviderDstu2Test;
+import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
+import ca.uhn.fhir.model.dstu2.composite.CodingDt;
+import ca.uhn.fhir.model.dstu2.resource.Bundle;
+import ca.uhn.fhir.model.dstu2.resource.ValueSet;
+import ca.uhn.fhir.model.primitive.CodeDt;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.model.primitive.StringDt;
+import ca.uhn.fhir.model.primitive.UriDt;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration({ "/hapi-fhir-server-resourceproviders-dstu2.xml", "/fhir-jpabase-spring-test-config.xml" })
+public class FhirResourceDaoValueSetDstu2Test {
+
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoValueSetDstu2Test.class);
+
+ private static IIdType vsid;
+
+ @Autowired
+ private FhirContext myCtx;
+
+ @Autowired
+ @Qualifier("mySystemDaoDstu2")
+ private IFhirSystemDao mySystemDao;
+
+ @Autowired
+ @Qualifier("myValueSetDaoDstu2")
+ private IFhirResourceDaoValueSet myValueSetDao;
+
+ @Before
+ @Transactional
+ public void before01() {
+ if (vsid == null) {
+ FhirSystemDaoDstu2Test.doDeleteEverything(mySystemDao);
+ }
+ }
+
+ @Before
+ @Transactional
+ public void before02() {
+ if (vsid == null) {
+ ValueSet upload = myCtx.newXmlParser().parseResource(ValueSet.class, new InputStreamReader(ResourceProviderDstu2Test.class.getResourceAsStream("/extensional-case-2.xml")));
+ upload.setId("");
+ vsid = myValueSetDao.create(upload).getId().toUnqualifiedVersionless();
+ }
+ }
+
+ @Test
+ public void testValidateCodeOperationByCodeAndSystemBad() {
+ UriDt valueSetIdentifier = null;
+ IdDt id = null;
+ CodeDt code = new CodeDt("8450-9-XXX");
+ UriDt system = new UriDt("http://loinc.org");
+ StringDt display = null;
+ CodingDt coding = null;
+ CodeableConceptDt codeableConcept = null;
+ ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept);
+ assertFalse(result.isResult());
+ }
+
+ @Test
+ public void testValidateCodeOperationByCodeAndSystemGood() {
+ UriDt valueSetIdentifier = null;
+ IdDt id = null;
+ CodeDt code = new CodeDt("8450-9");
+ UriDt system = new UriDt("http://loinc.org");
+ StringDt display = null;
+ CodingDt coding = null;
+ CodeableConceptDt codeableConcept = null;
+ ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept);
+ assertTrue(result.isResult());
+ assertEquals("Systolic blood pressure--expiration", result.getDisplay());
+ }
+
+ @Test
+ public void testValidateCodeOperationByIdentifierAndCodeAndSystem() {
+ UriDt valueSetIdentifier = new UriDt("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
+ IdDt id = null;
+ CodeDt code = new CodeDt("11378-7");
+ UriDt system = new UriDt("http://loinc.org");
+ StringDt display = null;
+ CodingDt coding = null;
+ CodeableConceptDt codeableConcept = null;
+ ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept);
+ assertTrue(result.isResult());
+ assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
+ }
+
+ @Test
+ public void testValidateCodeOperationByIdentifierAndCodeAndSystemAndBadDisplay() {
+ UriDt valueSetIdentifier = new UriDt("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
+ IdDt id = null;
+ CodeDt code = new CodeDt("11378-7");
+ UriDt system = new UriDt("http://loinc.org");
+ StringDt display = new StringDt("Systolic blood pressure at First encounterXXXX");
+ CodingDt coding = null;
+ CodeableConceptDt codeableConcept = null;
+ ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept);
+ assertFalse(result.isResult());
+ assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
+ }
+
+ @Test
+ public void testValidateCodeOperationByIdentifierAndCodeAndSystemAndGoodDisplay() {
+ UriDt valueSetIdentifier = new UriDt("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2");
+ IdDt id = null;
+ CodeDt code = new CodeDt("11378-7");
+ UriDt system = new UriDt("http://loinc.org");
+ StringDt display = new StringDt("Systolic blood pressure at First encounter");
+ CodingDt coding = null;
+ CodeableConceptDt codeableConcept = null;
+ ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept);
+ assertTrue(result.isResult());
+ assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
+ }
+
+ @Test
+ public void testValidateCodeOperationByResourceIdAndCodeableConcept() {
+ UriDt valueSetIdentifier = null;
+ IIdType id = vsid;
+ CodeDt code = null;
+ UriDt system = null;
+ StringDt display = null;
+ CodingDt coding = null;
+ CodeableConceptDt codeableConcept = new CodeableConceptDt("http://loinc.org", "11378-7");
+ ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept);
+ assertTrue(result.isResult());
+ assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
+ }
+
+ @Test
+ public void testValidateCodeOperationByResourceIdAndCodeAndSystem() {
+ UriDt valueSetIdentifier = null;
+ IIdType id = vsid;
+ CodeDt code = new CodeDt("11378-7");
+ UriDt system = new UriDt("http://loinc.org");
+ StringDt display = null;
+ CodingDt coding = null;
+ CodeableConceptDt codeableConcept = null;
+ ValidateCodeResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept);
+ assertTrue(result.isResult());
+ assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
+ }
+
+ @Test
+ public void testValueSetExpandOperation() throws IOException {
+ String resp;
+
+ ValueSet expanded = myValueSetDao.expand(vsid, null);
+ resp = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
+ ourLog.info(resp);
+ // @formatter:off
+ assertThat(resp,
+ stringContainsInOrder("",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "
",
+ "",
+ "",
+ "",
+ "",
+ "
",
+ ""
+ ));
+ //@formatter:on
+
+ /*
+ * Filter with display name
+ */
+
+ expanded = myValueSetDao.expand(vsid, new StringDt("systolic"));
+ resp = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
+ ourLog.info(resp);
+ //@formatter:off
+ assertThat(resp, stringContainsInOrder(
+ "",
+ ""));
+ //@formatter:on
+
+ /*
+ * Filter with code
+ */
+
+ expanded = myValueSetDao.expand(vsid, new StringDt("11378"));
+ resp = myCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
+ ourLog.info(resp);
+ //@formatter:off
+ assertThat(resp, stringContainsInOrder(
+ "",
+ ""));
+ //@formatter:on
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java
index 2891babeb2a..1661d9afd08 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java
@@ -428,8 +428,6 @@ public class ResourceProviderDstu2Test extends BaseJpaTest {
@Test
public void testCreateQuestionnaireResponseWithValidation() throws IOException {
- String methodName = "testCreateQuestionnaireResponseWithValidation";
-
ValueSet options = new ValueSet();
options.getCodeSystem().setSystem("urn:system").addConcept().setCode("code0");
IIdType optId = ourClient.create().resource(options).execute().getId();
@@ -1508,21 +1506,21 @@ public class ResourceProviderDstu2Test extends BaseJpaTest {
ourLog.info(resp);
assertEquals(200, response.getStatusLine().getStatusCode());
// @formatter:off
- assertThat(
- resp,
- stringContainsInOrder("",
- "",
- "",
+ assertThat(resp,
+ stringContainsInOrder("",
+ "",
+ "",
"",
- "",
"",
"",
- "
",
- "",
+ "",
+ "",
+ "",
"",
"",
- ""
- ));
+ "
",
+ ""
+ ));
//@formatter:on
} finally {
IOUtils.closeQuietly(response.getEntity().getContent());
diff --git a/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans.vm b/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans.vm
index 57611d036a2..5e65e4395af 100644
--- a/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans.vm
+++ b/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans.vm
@@ -28,7 +28,8 @@
#foreach ( $res in $resources )
#else
class="ca.uhn.fhir.jpa.dao.FhirResourceDao${versionCapitalized}">
diff --git a/pom.xml b/pom.xml
index 8e3718f1bc6..19c1a7b0006 100644
--- a/pom.xml
+++ b/pom.xml
@@ -336,6 +336,11 @@
spring-orm
${spring_version}
+
+ org.springframework
+ spring-test
+ ${spring_version}
+
org.springframework
spring-tx
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 707bbee1838..60f40dfd65e 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -93,6 +93,9 @@
a resourceType element) fail to parse with a confusing NullPointerException.
Thanks to GitHub user @hugosoares for reporting!
+
+ JPA server now implements the $validate-code operation
+