From 70e942e9f8df7f69959410486cddc720392a883e Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Sat, 19 Sep 2015 11:00:57 -0400
Subject: [PATCH 01/13] Version bump to 1.3-SNAPSHOT
---
examples/pom.xml | 10 ++--
hapi-deployable-pom/pom.xml | 2 +-
hapi-fhir-android/pom.xml | 8 +--
hapi-fhir-base-test-mindeps-client/pom.xml | 8 +--
hapi-fhir-base-test-mindeps-server/pom.xml | 8 +--
hapi-fhir-base/pom.xml | 2 +-
hapi-fhir-cli/pom.xml | 16 +++---
hapi-fhir-cobertura/pom.xml | 14 ++---
hapi-fhir-dist/pom.xml | 8 +--
hapi-fhir-jpaserver-base/pom.xml | 18 +++---
hapi-fhir-jpaserver-example/pom.xml | 12 ++--
hapi-fhir-jpaserver-uhnfhirtest/pom.xml | 19 ++-----
hapi-fhir-osgi-core/pom.xml | 57 +++++++++++++++++--
.../META-INF/{MANIFEST.MF => MANIFEST.MF__} | 0
hapi-fhir-structures-dstu/pom.xml | 6 +-
hapi-fhir-structures-dstu2/pom.xml | 8 +--
hapi-fhir-structures-hl7org-dstu2/pom.xml | 6 +-
hapi-fhir-testpage-overlay/pom.xml | 17 ++----
hapi-fhir-validation-resources-dstu2/pom.xml | 2 +-
hapi-tinder-plugin/pom.xml | 4 +-
hapi-tinder-test/pom.xml | 10 ++--
pom.xml | 9 ++-
restful-server-example-test/pom.xml | 6 +-
restful-server-example/pom.xml | 10 ++--
24 files changed, 148 insertions(+), 112 deletions(-)
rename hapi-fhir-osgi-core/src/main/resources/META-INF/{MANIFEST.MF => MANIFEST.MF__} (100%)
diff --git a/examples/pom.xml b/examples/pom.xml
index 79b0490d5fe..a0e3908996b 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-fhir
- 1.2
+ 1.3-SNAPSHOT
../pom.xml
@@ -18,22 +18,22 @@
ca.uhn.hapi.fhir
hapi-fhir-base
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-structures-dstu2
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-structures-hl7org-dstu2
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-validation-resources-dstu2
- 1.2
+ 1.3-SNAPSHOT
javax.servlet
diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml
index 87deb1fd76b..99ab0ccd399 100644
--- a/hapi-deployable-pom/pom.xml
+++ b/hapi-deployable-pom/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-fhir
- 1.2
+ 1.3-SNAPSHOT
../pom.xml
diff --git a/hapi-fhir-android/pom.xml b/hapi-fhir-android/pom.xml
index 1448c8c9f63..29fbcbcfc3a 100644
--- a/hapi-fhir-android/pom.xml
+++ b/hapi-fhir-android/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-fhir
- 1.2
+ 1.3-SNAPSHOT
../pom.xml
@@ -18,7 +18,7 @@
ca.uhn.hapi.fhir
hapi-fhir-base
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-structures-dstu
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-structures-dstu2
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-jpaserver-base
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-testpage-overlay
- 1.2
+ 1.3-SNAPSHOT
war
provided
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml
index c3c4e747871..c48ff1618c3 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml
+++ b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-fhir
- 1.2
+ 1.3-SNAPSHOT
../pom.xml
@@ -18,27 +18,27 @@
ca.uhn.hapi.fhir
hapi-fhir-jpaserver-base
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-structures-dstu
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-structures-dstu2
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-structures-hl7org-dstu2
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-testpage-overlay
- 1.2
+ 1.3-SNAPSHOT
war
provided
@@ -52,13 +52,6 @@
phloc-commons
-
-
org.springframework
spring-web
diff --git a/hapi-fhir-osgi-core/pom.xml b/hapi-fhir-osgi-core/pom.xml
index 64343583189..0d93f4896fc 100644
--- a/hapi-fhir-osgi-core/pom.xml
+++ b/hapi-fhir-osgi-core/pom.xml
@@ -1,31 +1,76 @@
-
+
4.0.0
ca.uhn.hapi.fhir
hapi-deployable-pom
- 1.2-SNAPSHOT
+ 1.3-SNAPSHOT
../hapi-deployable-pom/pom.xml
hapi-fhir-osgi-core
- jar
+ bundle
http://jamesagnew.github.io/hapi-fhir/
HAPI FHIR - OSGi Bundle
-
ca.uhn.hapi.fhir
hapi-fhir-base
- 1.2-SNAPSHOT
+ 1.3-SNAPSHOT
+
+
+ ca.uhn.hapi.fhir
+ hapi-fhir-structures-dstu
+ 1.3-SNAPSHOT
+
+
+ ca.uhn.hapi.fhir
+ hapi-fhir-structures-dstu2
+ 1.3-SNAPSHOT
+
+
+ ca.uhn.hapi.fhir
+ hapi-fhir-structures-hl7org-dstu2
+ 1.3-SNAPSHOT
+
+
+ ca.uhn.hapi.fhir
+ hapi-fhir-validation-resources-dstu2
+ 1.3-SNAPSHOT
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ true
+
+
+ ca.uhn.fhir
+ org.hl7.fhir
+ ${pom.artifactId}
+ *;scope=!provided|test
+ lib
+ true
+ <_removeheaders>Built-By
+
+
+
+
+
+
+ bundle
+
+ package
+
+
+
+
src/main/resources
diff --git a/hapi-fhir-osgi-core/src/main/resources/META-INF/MANIFEST.MF b/hapi-fhir-osgi-core/src/main/resources/META-INF/MANIFEST.MF__
similarity index 100%
rename from hapi-fhir-osgi-core/src/main/resources/META-INF/MANIFEST.MF
rename to hapi-fhir-osgi-core/src/main/resources/META-INF/MANIFEST.MF__
diff --git a/hapi-fhir-structures-dstu/pom.xml b/hapi-fhir-structures-dstu/pom.xml
index 3b76a30dade..78bbaca4bd9 100644
--- a/hapi-fhir-structures-dstu/pom.xml
+++ b/hapi-fhir-structures-dstu/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 1.2
+ 1.3-SNAPSHOT
../hapi-deployable-pom/pom.xml
@@ -17,7 +17,7 @@
ca.uhn.hapi.fhir
hapi-fhir-base
- 1.2
+ 1.3-SNAPSHOT
@@ -136,7 +136,7 @@
ca.uhn.hapi.fhir
hapi-tinder-plugin
- 1.2
+ 1.3-SNAPSHOT
diff --git a/hapi-fhir-structures-dstu2/pom.xml b/hapi-fhir-structures-dstu2/pom.xml
index 7534f06a29f..afe1059abeb 100644
--- a/hapi-fhir-structures-dstu2/pom.xml
+++ b/hapi-fhir-structures-dstu2/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 1.2
+ 1.3-SNAPSHOT
../hapi-deployable-pom/pom.xml
@@ -17,13 +17,13 @@
ca.uhn.hapi.fhir
hapi-fhir-base
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-validation-resources-dstu2
- 1.2
+ 1.3-SNAPSHOT
test
@@ -144,7 +144,7 @@
ca.uhn.hapi.fhir
hapi-tinder-plugin
- 1.2
+ 1.3-SNAPSHOT
generate
diff --git a/hapi-fhir-structures-hl7org-dstu2/pom.xml b/hapi-fhir-structures-hl7org-dstu2/pom.xml
index 7c3ea94a0d7..71ce3eff461 100644
--- a/hapi-fhir-structures-hl7org-dstu2/pom.xml
+++ b/hapi-fhir-structures-hl7org-dstu2/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 1.2
+ 1.3-SNAPSHOT
../hapi-deployable-pom/pom.xml
@@ -18,12 +18,12 @@
ca.uhn.hapi.fhir
hapi-fhir-base
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-validation-resources-dstu2
- 1.2
+ 1.3-SNAPSHOT
test
diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml
index ea5466fb44b..78039287a3c 100644
--- a/hapi-fhir-testpage-overlay/pom.xml
+++ b/hapi-fhir-testpage-overlay/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhir
hapi-fhir
- 1.2
+ 1.3-SNAPSHOT
../pom.xml
@@ -27,22 +27,22 @@
ca.uhn.hapi.fhir
hapi-fhir-base
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-jpaserver-base
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-structures-dstu
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-structures-dstu2
- 1.2
+ 1.3-SNAPSHOT
-
ch.qos.logback
logback-classic
diff --git a/hapi-fhir-validation-resources-dstu2/pom.xml b/hapi-fhir-validation-resources-dstu2/pom.xml
index ebdd42d0a0a..c7414794597 100644
--- a/hapi-fhir-validation-resources-dstu2/pom.xml
+++ b/hapi-fhir-validation-resources-dstu2/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhir
hapi-deployable-pom
- 1.2
+ 1.3-SNAPSHOT
../hapi-deployable-pom/pom.xml
diff --git a/hapi-tinder-plugin/pom.xml b/hapi-tinder-plugin/pom.xml
index ce971faff82..0e7d9772b68 100644
--- a/hapi-tinder-plugin/pom.xml
+++ b/hapi-tinder-plugin/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
hapi-fhir
- 1.2
+ 1.3-SNAPSHOT
../pom.xml
@@ -19,7 +19,7 @@
ca.uhn.hapi.fhir
hapi-fhir-base
- 1.2
+ 1.3-SNAPSHOT
+ hapi-fhir-osgi-core
diff --git a/restful-server-example-test/pom.xml b/restful-server-example-test/pom.xml
index 03635d4cbce..f185b8d387f 100644
--- a/restful-server-example-test/pom.xml
+++ b/restful-server-example-test/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhir
hapi-fhir
- 1.2
+ 1.3-SNAPSHOT
../pom.xml
@@ -17,12 +17,12 @@
ca.uhn.hapi.fhir
hapi-fhir-base
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-structures-dstu2
- 1.2
+ 1.3-SNAPSHOT
test
diff --git a/restful-server-example/pom.xml b/restful-server-example/pom.xml
index 39c04a924d7..aa9dccc6af2 100644
--- a/restful-server-example/pom.xml
+++ b/restful-server-example/pom.xml
@@ -8,13 +8,13 @@
ca.uhn.hapi.fhir
hapi-fhir
- 1.2
+ 1.3-SNAPSHOT
../pom.xml
ca.uhn.hapi.fhir
restful-server-example
- 1.2
+ 1.3-SNAPSHOT
war
HAPI FHIR Sample RESTful Server
@@ -35,20 +35,20 @@
ca.uhn.hapi.fhir
hapi-fhir-base
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-structures-dstu2
- 1.2
+ 1.3-SNAPSHOT
ca.uhn.hapi.fhir
hapi-fhir-testpage-overlay
- 1.2
+ 1.3-SNAPSHOT
war
provided
From d59c0ff404ce9deb13e0bc9a975dbd0c9b53e229 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Sun, 20 Sep 2015 08:23:03 -0400
Subject: [PATCH 02/13] Correctly index reference paths with multiple paths,
and don't store duplicate indexes in JPA
---
.../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 190 +++++++++---------
.../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 1 -
.../fhir/jpa/dao/ISearchParamExtractor.java | 37 +---
.../jpa/dao/SearchParamExtractorDstu1.java | 28 +--
.../jpa/dao/SearchParamExtractorDstu2.java | 36 ++--
.../ResourceIndexedSearchParamCoords.java | 54 ++++-
.../ResourceIndexedSearchParamDate.java | 50 ++++-
.../ResourceIndexedSearchParamNumber.java | 45 ++++-
.../ResourceIndexedSearchParamQuantity.java | 51 ++++-
.../ResourceIndexedSearchParamString.java | 58 ++++--
.../ResourceIndexedSearchParamToken.java | 49 ++++-
.../entity/ResourceIndexedSearchParamUri.java | 30 +++
.../ca/uhn/fhir/jpa/entity/ResourceLink.java | 73 ++++---
.../ca/uhn/fhir/jpa/entity/ResourceTable.java | 8 +-
.../ca/uhn/fhir/jpa/dao/BaseJpaDstu2Test.java | 17 +-
.../dao/FhirResourceDaoDstu2SearchTest.java | 167 +++++++++++++++
.../uhn/fhir/rest/server/SearchDstu2Test.java | 67 +++++-
src/changes/changes.xml | 11 +
18 files changed, 748 insertions(+), 224 deletions(-)
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 952362ae003..f0c4503a175 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
@@ -166,8 +166,8 @@ public abstract class BaseHapiFhirDao implements IDao {
return InstantDt.withCurrentTime();
}
- protected List extractResourceLinks(ResourceTable theEntity, IResource theResource) {
- ArrayList retVal = new ArrayList();
+ protected Set extractResourceLinks(ResourceTable theEntity, IResource theResource) {
+ Set retVal = new HashSet();
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource);
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) {
@@ -185,73 +185,76 @@ public abstract class BaseHapiFhirDao implements IDao {
multiType = true;
}
- for (Object nextObject : extractValues(nextPathsUnsplit, theResource)) {
- if (nextObject == null) {
- continue;
- }
-
- ResourceLink nextEntity;
- if (nextObject instanceof BaseResourceReferenceDt) {
- BaseResourceReferenceDt nextValue = (BaseResourceReferenceDt) nextObject;
- if (nextValue.isEmpty()) {
- continue;
- }
- if (nextValue.getReference().isEmpty() || nextValue.getReference().getValue().startsWith("#")) {
- // This is a blank or contained resource reference
+ String[] nextPathsSplit = nextPathsUnsplit.split("\\|");
+ for (String nextPath : nextPathsSplit) {
+ for (Object nextObject : extractValues(nextPath, theResource)) {
+ if (nextObject == null) {
continue;
}
- String typeString = nextValue.getReference().getResourceType();
- if (isBlank(typeString)) {
- throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Does not contain resource type - " + nextValue.getReference().getValue());
- }
- RuntimeResourceDefinition resourceDefinition;
- try {
- resourceDefinition = getContext().getResourceDefinition(typeString);
- } catch (DataFormatException e) {
- throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Resource type is unknown or not supported on this server - " + nextValue.getReference().getValue());
- }
+ ResourceLink nextEntity;
+ if (nextObject instanceof BaseResourceReferenceDt) {
+ BaseResourceReferenceDt nextValue = (BaseResourceReferenceDt) nextObject;
+ if (nextValue.isEmpty()) {
+ continue;
+ }
+ if (nextValue.getReference().isEmpty() || nextValue.getReference().getValue().startsWith("#")) {
+ // This is a blank or contained resource reference
+ continue;
+ }
- Class extends IBaseResource> type = resourceDefinition.getImplementingClass();
- String id = nextValue.getReference().getIdPart();
- if (StringUtils.isBlank(id)) {
- throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Does not contain resource ID - " + nextValue.getReference().getValue());
- }
+ String typeString = nextValue.getReference().getResourceType();
+ if (isBlank(typeString)) {
+ throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Does not contain resource type - " + nextValue.getReference().getValue());
+ }
+ RuntimeResourceDefinition resourceDefinition;
+ try {
+ resourceDefinition = getContext().getResourceDefinition(typeString);
+ } catch (DataFormatException e) {
+ throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Resource type is unknown or not supported on this server - " + nextValue.getReference().getValue());
+ }
- IFhirResourceDao> dao = getDao(type);
- if (dao == null) {
- StringBuilder b = new StringBuilder();
- b.append("This server (version ");
- b.append(myContext.getVersion().getVersion());
- b.append(") is not able to handle resources of type[");
- b.append(nextValue.getReference().getResourceType());
- b.append("] - Valid resource types for this server: ");
- b.append(myResourceTypeToDao.keySet().toString());
+ Class extends IBaseResource> type = resourceDefinition.getImplementingClass();
+ String id = nextValue.getReference().getIdPart();
+ if (StringUtils.isBlank(id)) {
+ throw new InvalidRequestException("Invalid resource reference found at path[" + nextPathsUnsplit + "] - Does not contain resource ID - " + nextValue.getReference().getValue());
+ }
- throw new InvalidRequestException(b.toString());
- }
- Long valueOf;
- try {
- valueOf = translateForcedIdToPid(nextValue.getReference());
- } catch (ResourceNotFoundException e) {
- String resName = getContext().getResourceDefinition(type).getName();
- throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
- }
- ResourceTable target = myEntityManager.find(ResourceTable.class, valueOf);
- if (target == null) {
- String resName = getContext().getResourceDefinition(type).getName();
- throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
- }
- nextEntity = new ResourceLink(nextPathsUnsplit, theEntity, target);
- } else {
- if (!multiType) {
- throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());
+ IFhirResourceDao> dao = getDao(type);
+ if (dao == null) {
+ StringBuilder b = new StringBuilder();
+ b.append("This server (version ");
+ b.append(myContext.getVersion().getVersion());
+ b.append(") is not able to handle resources of type[");
+ b.append(nextValue.getReference().getResourceType());
+ b.append("] - Valid resource types for this server: ");
+ b.append(myResourceTypeToDao.keySet().toString());
+
+ throw new InvalidRequestException(b.toString());
+ }
+ Long valueOf;
+ try {
+ valueOf = translateForcedIdToPid(nextValue.getReference());
+ } catch (ResourceNotFoundException e) {
+ String resName = getContext().getResourceDefinition(type).getName();
+ throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
+ }
+ ResourceTable target = myEntityManager.find(ResourceTable.class, valueOf);
+ if (target == null) {
+ String resName = getContext().getResourceDefinition(type).getName();
+ throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
+ }
+ nextEntity = new ResourceLink(nextPath, theEntity, target);
} else {
- continue;
+ if (!multiType) {
+ throw new ConfigurationException("Search param " + nextSpDef.getName() + " is of unexpected datatype: " + nextObject.getClass());
+ } else {
+ continue;
+ }
+ }
+ if (nextEntity != null) {
+ retVal.add(nextEntity);
}
- }
- if (nextEntity != null) {
- retVal.add(nextEntity);
}
}
}
@@ -261,46 +264,43 @@ public abstract class BaseHapiFhirDao implements IDao {
return retVal;
}
- protected List extractSearchParamDates(ResourceTable theEntity, IResource theResource) {
+ protected Set extractSearchParamDates(ResourceTable theEntity, IResource theResource) {
return mySearchParamExtractor.extractSearchParamDates(theEntity, theResource);
}
- protected List extractSearchParamNumber(ResourceTable theEntity, IResource theResource) {
+ protected Set extractSearchParamNumber(ResourceTable theEntity, IResource theResource) {
return mySearchParamExtractor.extractSearchParamNumber(theEntity, theResource);
}
- protected List extractSearchParamUri(ResourceTable theEntity, IResource theResource) {
+ protected Set extractSearchParamUri(ResourceTable theEntity, IResource theResource) {
return mySearchParamExtractor.extractSearchParamUri(theEntity, theResource);
}
- protected List extractSearchParamCoords(ResourceTable theEntity, IResource theResource) {
+ protected Set extractSearchParamCoords(ResourceTable theEntity, IResource theResource) {
return mySearchParamExtractor.extractSearchParamCoords(theEntity, theResource);
}
- protected List extractSearchParamQuantity(ResourceTable theEntity, IResource theResource) {
+ protected Set extractSearchParamQuantity(ResourceTable theEntity, IResource theResource) {
return mySearchParamExtractor.extractSearchParamQuantity(theEntity, theResource);
}
- protected List extractSearchParamStrings(ResourceTable theEntity, IResource theResource) {
+ protected Set extractSearchParamStrings(ResourceTable theEntity, IResource theResource) {
return mySearchParamExtractor.extractSearchParamStrings(theEntity, theResource);
}
- protected List extractSearchParamTokens(ResourceTable theEntity, IResource theResource) {
+ protected Set extractSearchParamTokens(ResourceTable theEntity, IResource theResource) {
return mySearchParamExtractor.extractSearchParamTokens(theEntity, theResource);
}
- private List
+
+ DIST
+
+
+
+ de.juplo
+ hibernate4-maven-plugin
+
+ true
+ SCRIPT
+ ${skip-hib4}
+
+
+
+
+ o10g
+
+ export
+
+ test
+
+ org.hibernate.dialect.Oracle10gDialect
+ ${project.build.directory}/schema_oracle_10g.sql
+
+
+
+ derby
+
+ export
+
+ test
+
+ org.hibernate.dialect.DerbyTenSevenDialect
+ ${project.build.directory}/schema_derby.sql
+
+
+
+ hsql
+
+ export
+
+ test
+
+ org.hibernate.dialect.HSQLDialect
+ ${project.build.directory}/schema_hsql.sql
+
+
+
+ mysql5
+
+ export
+
+ test
+
+ org.hibernate.dialect.MySQL5Dialect
+ ${project.build.directory}/schema_mysql_5.sql
+
+
+
+
+
+
+
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 f0c4503a175..21f7c0866ba 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
@@ -87,6 +87,7 @@ import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.entity.ResourceLink;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.ResourceTag;
+import ca.uhn.fhir.jpa.entity.SubscriptionCandidateResource;
import ca.uhn.fhir.jpa.entity.TagDefinition;
import ca.uhn.fhir.jpa.entity.TagTypeEnum;
import ca.uhn.fhir.jpa.util.StopWatch;
@@ -520,7 +521,7 @@ public abstract class BaseHapiFhirDao implements IDao {
}
throw e;
}
- IResource resource = (IResource) toResource(type.getImplementingClass(), next);
+ IResource resource = (IResource) toResource(type.getImplementingClass(), next, true);
retVal.add(resource);
}
return retVal;
@@ -558,12 +559,6 @@ public abstract class BaseHapiFhirDao implements IDao {
}
protected void populateResourceIntoEntity(IResource theResource, ResourceTable theEntity) {
-
- if (theEntity.getPublished().isEmpty()) {
- theEntity.setPublished(new Date());
- }
- theEntity.setUpdated(new Date());
-
theEntity.setResourceType(toResourceName(theResource));
List refs = myContext.newTerser().getAllPopulatedChildElementsOfType(theResource, BaseResourceReferenceDt.class);
@@ -936,13 +931,13 @@ public abstract class BaseHapiFhirDao implements IDao {
return retVal;
}
- protected IBaseResource toResource(BaseHasResource theEntity) {
+ protected IBaseResource toResource(BaseHasResource theEntity, boolean theForHistoryOperation) {
RuntimeResourceDefinition type = myContext.getResourceDefinition(theEntity.getResourceType());
- return toResource(type.getImplementingClass(), theEntity);
+ return toResource(type.getImplementingClass(), theEntity, theForHistoryOperation);
}
@SuppressWarnings("unchecked")
- protected R toResource(Class theResourceType, BaseHasResource theEntity) {
+ protected R toResource(Class theResourceType, BaseHasResource theEntity, boolean theForHistoryOperation) {
String resourceText = null;
switch (theEntity.getEncoding()) {
case JSON:
@@ -983,9 +978,23 @@ public abstract class BaseHapiFhirDao implements IDao {
res = (IResource) myContext.getResourceDefinition(theResourceType).newInstance();
retVal = (R) res;
ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted()));
- ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.DELETE);
+ if (theForHistoryOperation) {
+ ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.DELETE);
+ }
+ } else if (theForHistoryOperation) {
+ /*
+ * If the create and update times match, this was when the resource was created
+ * so we should mark it as a POST. Otherwise, it's a PUT.
+ */
+ Date published = theEntity.getPublished().getValue();
+ Date updated = theEntity.getUpdated().getValue();
+ if (published.equals(updated)) {
+ ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.POST);
+ } else {
+ ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.PUT);
+ }
}
-
+
res.setId(theEntity.getIdDt());
ResourceMetadataKeyEnum.VERSION.put(res, Long.toString(theEntity.getVersion()));
@@ -1063,25 +1072,28 @@ public abstract class BaseHapiFhirDao implements IDao {
}
}
- protected ResourceTable updateEntity(final IResource theResource, ResourceTable entity, boolean theUpdateHistory, Date theDeletedTimestampOrNull) {
- return updateEntity(theResource, entity, theUpdateHistory, theDeletedTimestampOrNull, true, true);
+ protected ResourceTable updateEntity(final IResource theResource, ResourceTable entity, boolean theUpdateHistory, Date theDeletedTimestampOrNull, Date theUpdateTime) {
+ return updateEntity(theResource, entity, theUpdateHistory, theDeletedTimestampOrNull, true, true, theUpdateTime);
}
@SuppressWarnings("unchecked")
- protected ResourceTable updateEntity(final IResource theResource, ResourceTable theEntity, boolean theUpdateHistory, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion) {
-
- if (theEntity.getPublished() == null) {
- theEntity.setPublished(new Date());
- }
-
+ protected ResourceTable updateEntity(final IResource theResource, ResourceTable theEntity, boolean theUpdateHistory, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion, Date theUpdateTime) {
+
+ /*
+ * This should be the very first thing..
+ */
if (theResource != null) {
- validateResourceForStorage((T) theResource);
+ validateResourceForStorage((T) theResource, theEntity);
String resourceType = myContext.getResourceDefinition(theResource).getName();
if (isNotBlank(theEntity.getResourceType()) && !theEntity.getResourceType().equals(resourceType)) {
throw new UnprocessableEntityException("Existing resource ID[" + theEntity.getIdDt().toUnqualifiedVersionless() + "] is of type[" + theEntity.getResourceType() + "] - Cannot update with [" + resourceType + "]");
}
}
+ if (theEntity.getPublished() == null) {
+ theEntity.setPublished(theUpdateTime);
+ }
+
if (theUpdateHistory) {
final ResourceHistoryTable historyEntry = theEntity.toHistory();
myEntityManager.persist(historyEntry);
@@ -1159,7 +1171,7 @@ public abstract class BaseHapiFhirDao implements IDao {
links = extractResourceLinks(theEntity, theResource);
populateResourceIntoEntity(theResource, theEntity);
- theEntity.setUpdated(new Date());
+ theEntity.setUpdated(theUpdateTime);
theEntity.setLanguage(theResource.getLanguage().getValue());
theEntity.setParamsString(stringParams);
theEntity.setParamsStringPopulated(stringParams.isEmpty() == false);
@@ -1178,11 +1190,11 @@ public abstract class BaseHapiFhirDao implements IDao {
theEntity.setResourceLinks(links);
theEntity.setHasLinks(links.isEmpty() == false);
theEntity.setIndexStatus(INDEX_STATUS_INDEXED);
-
+
} else {
populateResourceIntoEntity(theResource, theEntity);
- theEntity.setUpdated(new Date());
+ theEntity.setUpdated(theUpdateTime);
theEntity.setLanguage(theResource.getLanguage().getValue());
theEntity.setIndexStatus(null);
@@ -1197,8 +1209,24 @@ public abstract class BaseHapiFhirDao implements IDao {
myEntityManager.persist(theEntity.getForcedId());
}
+ postPersist(theEntity, (T) theResource);
+
} else {
theEntity = myEntityManager.merge(theEntity);
+
+ postUpdate(theEntity, (T) theResource);
+ }
+
+ /*
+ * When subscription is enabled, for each resource we store we also
+ * store a subscription candidate. These are examined by the subscription
+ * module and then deleted.
+ */
+ if (myConfig.isSubscriptionEnabled() && thePerformIndexing) {
+ SubscriptionCandidateResource candidate = new SubscriptionCandidateResource();
+ candidate.setResource(theEntity);
+ candidate.setResourceVersion(theEntity.getVersion());
+ myEntityManager.persist(candidate);
}
if (thePerformIndexing) {
@@ -1289,6 +1317,30 @@ public abstract class BaseHapiFhirDao implements IDao {
return theEntity;
}
+ /**
+ * Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the
+ * first time.
+ *
+ * @param theEntity
+ * The resource
+ * @param theResource The resource being persisted
+ */
+ protected void postUpdate(ResourceTable theEntity, T theResource) {
+ // nothing
+ }
+
+ /**
+ * Subclasses may override to provide behaviour. Called when a resource has been inserved into the database for the
+ * first time.
+ *
+ * @param theEntity
+ * The resource
+ * @param theResource The resource being persisted
+ */
+ protected void postPersist(ResourceTable theEntity, T theResource) {
+ // nothing
+ }
+
/**
* 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, checks for the "subsetted" tag and rejects
@@ -1296,8 +1348,9 @@ public abstract class BaseHapiFhirDao implements IDao {
*
* @param theResource
* The resource that is about to be persisted
+ * @param theEntityToSave TODO
*/
- protected void validateResourceForStorage(T theResource) {
+ protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) {
IResource res = (IResource) theResource;
TagList tagList = ResourceMetadataKeyEnum.TAG_LIST.get(res);
if (tagList != null) {
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 bd9977b30e4..f3baeae53c2 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
@@ -142,7 +142,7 @@ public abstract class BaseHapiFhirResourceDao extends BaseH
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirResourceDao.class);
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
- private EntityManager myEntityManager;
+ protected EntityManager myEntityManager;
@Autowired
private PlatformTransactionManager myPlatformTransactionManager;
@@ -1004,7 +1004,7 @@ public abstract class BaseHapiFhirResourceDao extends BaseH
}
}
- return doCreate(theResource, theIfNoneExist, thePerformIndexing);
+ return doCreate(theResource, theIfNoneExist, thePerformIndexing, new Date());
}
private Predicate createCompositeParamPart(CriteriaBuilder builder, Root from, RuntimeSearchParam left, IQueryParameterType leftValue) {
@@ -1268,7 +1268,8 @@ public abstract class BaseHapiFhirResourceDao extends BaseH
ActionRequestDetails requestDetails = new ActionRequestDetails(theId, theId.getResourceType());
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
- ResourceTable savedEntity = updateEntity(null, entity, true, new Date());
+ Date updateTime = new Date();
+ ResourceTable savedEntity = updateEntity(null, entity, true, updateTime, updateTime);
notifyWriteCompleted();
@@ -1298,14 +1299,15 @@ public abstract class BaseHapiFhirResourceDao extends BaseH
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
// Perform delete
- ResourceTable savedEntity = updateEntity(null, entity, true, new Date());
+ Date updateTime = new Date();
+ ResourceTable savedEntity = updateEntity(null, entity, true, updateTime, updateTime);
notifyWriteCompleted();
ourLog.info("Processed delete on {} in {}ms", theUrl, w.getMillisAndRestart());
return toMethodOutcome(savedEntity, null);
}
- private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing) {
+ private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing, Date theUpdateTime) {
StopWatch w = new StopWatch();
preProcessResourceForStorage(theResource);
@@ -1346,7 +1348,7 @@ public abstract class BaseHapiFhirResourceDao extends BaseH
ActionRequestDetails requestDetails = new ActionRequestDetails(theResource.getId(), toResourceName(theResource), theResource);
notifyInterceptors(RestOperationTypeEnum.CREATE, requestDetails);
- updateEntity(theResource, entity, false, null, thePerformIndexing, true);
+ updateEntity(theResource, entity, false, null, thePerformIndexing, true, theUpdateTime);
DaoMethodOutcome outcome = toMethodOutcome(entity, theResource).setCreated(true);
@@ -1419,7 +1421,7 @@ public abstract class BaseHapiFhirResourceDao extends BaseH
try {
BaseHasResource entity = readEntity(theId.toVersionless(), false);
validateResourceType(entity);
- currentTmp = toResource(myResourceType, entity);
+ currentTmp = toResource(myResourceType, entity, true);
if (ResourceMetadataKeyEnum.UPDATED.get(currentTmp).after(end.getValue())) {
currentTmp = null;
}
@@ -1496,7 +1498,7 @@ public abstract class BaseHapiFhirResourceDao extends BaseH
if (retVal.size() == maxResults) {
break;
}
- retVal.add(toResource(myResourceType, next));
+ retVal.add(toResource(myResourceType, next, true));
}
return retVal;
@@ -1527,7 +1529,7 @@ public abstract class BaseHapiFhirResourceDao extends BaseH
return retVal;
}
- private void loadResourcesByPid(Collection theIncludePids, List theResourceListToPopulate, Set theRevIncludedPids) {
+ private void loadResourcesByPid(Collection theIncludePids, List theResourceListToPopulate, Set theRevIncludedPids, boolean theForHistoryOperation) {
if (theIncludePids.isEmpty()) {
return;
}
@@ -1546,7 +1548,7 @@ public abstract class BaseHapiFhirResourceDao extends BaseH
for (ResourceTable next : q.getResultList()) {
Class extends IBaseResource> resourceType = getContext().getResourceDefinition(next.getResourceType()).getImplementingClass();
- IResource resource = (IResource) toResource(resourceType, next);
+ IResource resource = (IResource) toResource(resourceType, next, theForHistoryOperation);
Integer index = position.get(next.getId());
if (index == null) {
ourLog.warn("Got back unexpected resource PID {}", next.getId());
@@ -1827,7 +1829,7 @@ public abstract class BaseHapiFhirResourceDao extends BaseH
BaseHasResource entity = readEntity(theId);
validateResourceType(entity);
- T retVal = toResource(myResourceType, entity);
+ T retVal = toResource(myResourceType, entity, false);
InstantDt deleted = ResourceMetadataKeyEnum.DELETED_AT.get(retVal);
if (deleted != null && !deleted.isEmpty()) {
@@ -2065,7 +2067,7 @@ public abstract class BaseHapiFhirResourceDao extends BaseH
// Execute the query and make sure we return distinct results
List retVal = new ArrayList();
- loadResourcesByPid(pidsSubList, retVal, revIncludedPids);
+ loadResourcesByPid(pidsSubList, retVal, revIncludedPids, false);
return retVal;
}
@@ -2381,7 +2383,7 @@ public abstract class BaseHapiFhirResourceDao extends BaseH
if (resourceId.isIdPartValidLong()) {
throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedNumericId", theResource.getId().getIdPart()));
}
- return doCreate(theResource, null, thePerformIndexing);
+ return doCreate(theResource, null, thePerformIndexing, new Date());
}
}
@@ -2398,7 +2400,7 @@ public abstract class BaseHapiFhirResourceDao extends BaseH
notifyInterceptors(RestOperationTypeEnum.UPDATE, requestDetails);
// Perform update
- ResourceTable savedEntity = updateEntity(theResource, entity, true, null, thePerformIndexing, true);
+ ResourceTable savedEntity = updateEntity(theResource, entity, true, null, thePerformIndexing, true, new Date());
notifyWriteCompleted();
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java
index dc93764ff3b..02749018d08 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java
@@ -101,7 +101,7 @@ public abstract class BaseHapiFhirSystemDao extends BaseHapiFhirDao myInterceptors;
private ResourceEncodingEnum myResourceEncoding = ResourceEncodingEnum.JSONC;
+ private boolean mySubscriptionEnabled;
/**
* See {@link #setIncludeLimit(int)}
@@ -64,6 +65,13 @@ public class DaoConfig {
return myResourceEncoding;
}
+ /**
+ * See {@link #setSubscriptionEnabled(boolean)}
+ */
+ public boolean isSubscriptionEnabled() {
+ return mySubscriptionEnabled;
+ }
+
public void setHardSearchLimit(int theHardSearchLimit) {
myHardSearchLimit = theHardSearchLimit;
}
@@ -90,12 +98,12 @@ public class DaoConfig {
* ID).
*
*/
- public void setInterceptors(List theInterceptors) {
- myInterceptors = theInterceptors;
- }
-
- public void setResourceEncoding(ResourceEncodingEnum theResourceEncoding) {
- myResourceEncoding = theResourceEncoding;
+ public void setInterceptors(IServerInterceptor... theInterceptor) {
+ if (theInterceptor == null || theInterceptor.length==0){
+ setInterceptors(new ArrayList());
+ } else {
+ setInterceptors(Arrays.asList(theInterceptor));
+ }
}
/**
@@ -107,12 +115,23 @@ public class DaoConfig {
* ID).
*
*/
- public void setInterceptors(IServerInterceptor... theInterceptor) {
- if (theInterceptor == null || theInterceptor.length==0){
- setInterceptors(new ArrayList());
- } else {
- setInterceptors(Arrays.asList(theInterceptor));
- }
+ public void setInterceptors(List theInterceptors) {
+ myInterceptors = theInterceptors;
+ }
+
+ public void setResourceEncoding(ResourceEncodingEnum theResourceEncoding) {
+ myResourceEncoding = theResourceEncoding;
+ }
+
+ /**
+ * Does this server support subscription? If set to true, the server
+ * will enable the subscription monitoring mode, which adds a bit of
+ * overhead. Note that if this is enabled, you must also include
+ * Spring task scanning to your XML config for the scheduled tasks
+ * used by the subscription module.
+ */
+ public void setSubscriptionEnabled(boolean theSubscriptionEnabled) {
+ mySubscriptionEnabled = theSubscriptionEnabled;
}
}
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 0daa1bc776c..2fa92c6f9d6 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
@@ -27,6 +27,7 @@ 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.jpa.entity.ResourceTable;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.Questionnaire;
import ca.uhn.fhir.model.dstu2.resource.QuestionnaireResponse;
@@ -58,8 +59,8 @@ public class FhirResourceDaoQuestionnaireResponseDstu2 extends FhirResourceDaoDs
}
@Override
- protected void validateResourceForStorage(QuestionnaireResponse theResource) {
- super.validateResourceForStorage(theResource);
+ protected void validateResourceForStorage(QuestionnaireResponse theResource, ResourceTable theEntityToSave) {
+ super.validateResourceForStorage(theResource, theEntityToSave);
if (!myValidateResponses) {
return;
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
new file mode 100644
index 00000000000..32d72e2196f
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
@@ -0,0 +1,195 @@
+package ca.uhn.fhir.jpa.dao;
+
+import static org.apache.commons.lang3.StringUtils.isBlank;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+import javax.persistence.Query;
+import javax.persistence.TypedQuery;
+
+import org.apache.commons.lang3.Validate;
+import org.apache.commons.lang3.time.DateUtils;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import ca.uhn.fhir.context.RuntimeResourceDefinition;
+import ca.uhn.fhir.jpa.entity.ResourceTable;
+import ca.uhn.fhir.jpa.entity.SubscriptionTable;
+import ca.uhn.fhir.model.api.IResource;
+import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
+import ca.uhn.fhir.model.dstu2.resource.Subscription;
+import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.parser.DataFormatException;
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
+import ca.uhn.fhir.util.UrlUtil;
+import ca.uhn.fhir.util.UrlUtil.UrlParts;
+
+public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2implements IFhirResourceDaoSubscription {
+
+ private static final ResourceMetadataKeyEnum ALLOW_STATUS_CHANGE = new ResourceMetadataKeyEnum(FhirResourceDaoSubscriptionDstu2.class.getName() + "_ALLOW_STATUS_CHANGE") {
+ private static final long serialVersionUID = 1;
+
+ @Override
+ public Object get(IResource theResource) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void put(IResource theResource, Object theObject) {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSubscriptionDstu2.class);
+
+ private void createSubscriptionTable(ResourceTable theEntity, Subscription theSubscription) {
+ SubscriptionTable subscriptionEntity = new SubscriptionTable();
+ subscriptionEntity.setSubscriptionResource(theEntity);
+ subscriptionEntity.setNextCheck(theEntity.getPublished().getValue());
+ subscriptionEntity.setNextCheckSince(theEntity.getPublished().getValue());
+ subscriptionEntity.setStatus(theSubscription.getStatusElement().getValueAsEnum());
+ myEntityManager.persist(subscriptionEntity);
+ }
+
+ @Override
+ public SubscriptionTable getSubscriptionByResourceId(long theSubscriptionResourceId) {
+ TypedQuery q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_GET_BY_RES", SubscriptionTable.class);
+ q.setParameter("res_id", theSubscriptionResourceId);
+ return q.getSingleResult();
+ }
+
+
+
+ @Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND)
+ @Transactional(propagation = Propagation.NOT_SUPPORTED)
+ @Override
+ public void pollForNewUndeliveredResources() {
+ if (getConfig().isSubscriptionEnabled() == false) {
+ return;
+ }
+ ourLog.trace("Beginning pollForNewUndeliveredResources()");
+
+ TypedQuery q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_NEXT_CHECK", SubscriptionTable.class);
+ q.setParameter("next_check", new Date());
+ q.setParameter("status", SubscriptionStatusEnum.ACTIVE);
+ List subscriptions = q.getResultList();
+
+
+ }
+
+ @Override
+ protected void postPersist(ResourceTable theEntity, Subscription theSubscription) {
+ super.postPersist(theEntity, theSubscription);
+
+ createSubscriptionTable(theEntity, theSubscription);
+ }
+
+ @Override
+ public void setSubscriptionStatus(Long theResourceId, SubscriptionStatusEnum theStatus) {
+ Validate.notNull(theResourceId);
+ Validate.notNull(theStatus);
+
+ ResourceTable existing = readEntityLatestVersion(new IdDt("Subscription", theResourceId));
+ Subscription existingRes = toResource(Subscription.class, existing, false);
+
+ existingRes.getResourceMetadata().put(ALLOW_STATUS_CHANGE, new Object());
+ existingRes.setStatus(theStatus);
+
+ update(existingRes);
+ }
+
+ @Override
+ protected ResourceTable updateEntity(IResource theResource, ResourceTable theEntity, boolean theUpdateHistory, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion, Date theUpdateTime) {
+ ResourceTable retVal = super.updateEntity(theResource, theEntity, theUpdateHistory, theDeletedTimestampOrNull, thePerformIndexing, theUpdateVersion, theUpdateTime);
+
+ Subscription resource = (Subscription) theResource;
+ Long resourceId = theEntity.getId();
+ if (theDeletedTimestampOrNull != null) {
+ Query q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_DELETE");
+ q.setParameter("res_id", resourceId);
+ q.executeUpdate();
+ } else {
+ Query q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_SET_STATUS");
+ q.setParameter("res_id", resourceId);
+ q.setParameter("status", resource.getStatusElement().getValueAsEnum());
+ if (q.executeUpdate() > 0) {
+ ourLog.info("Updated subscription status for subscription {} to {}", resourceId, resource.getStatusElement().getValueAsEnum());
+ } else {
+ createSubscriptionTable(retVal, resource);
+ }
+ }
+ return retVal;
+ }
+
+ @Override
+ protected void validateResourceForStorage(Subscription theResource, ResourceTable theEntityToSave) {
+ super.validateResourceForStorage(theResource, theEntityToSave);
+
+ String query = theResource.getCriteria();
+ if (isBlank(query)) {
+ throw new UnprocessableEntityException("Subscription.criteria must be populated");
+ }
+
+ int sep = query.indexOf('?');
+ if (sep <= 1) {
+ throw new UnprocessableEntityException("Subscription.criteria must be in the form \"{Resource Type}?[params]\"");
+ }
+
+ String resType = query.substring(0, sep);
+ if (resType.contains("/")) {
+ throw new UnprocessableEntityException("Subscription.criteria must be in the form \"{Resource Type}?[params]\"");
+ }
+
+ RuntimeResourceDefinition resDef;
+ try {
+ resDef = getContext().getResourceDefinition(resType);
+ } catch (DataFormatException e) {
+ throw new UnprocessableEntityException("Subscription.criteria contains invalid/unsupported resource type: " + resType);
+ }
+
+ IFhirResourceDao extends IBaseResource> dao = getDao(resDef.getImplementingClass());
+ if (dao == null) {
+ throw new UnprocessableEntityException("Subscription.criteria contains invalid/unsupported resource type: " + resDef);
+ }
+
+// SearchParameterMap parsedUrl = translateMatchUrl(query, resDef);
+
+ if (theResource.getChannel().getType() == null) {
+ throw new UnprocessableEntityException("Subscription.channel.type must be populated on this server");
+ }
+
+ SubscriptionStatusEnum status = theResource.getStatusElement().getValueAsEnum();
+ Subscription existing = theEntityToSave.getEncoding() != null ? toResource(Subscription.class, theEntityToSave, false) : null;
+ if (status == null) {
+ // if (existing != null) {
+ // status = existing.getStatusElement().getValueAsEnum();
+ // theResource.setStatus(status);
+ // } else {
+ status = SubscriptionStatusEnum.REQUESTED;
+ theResource.setStatus(status);
+ // }
+ } else {
+ SubscriptionStatusEnum existingStatus = existing.getStatusElement().getValueAsEnum();
+ if (existingStatus != status) {
+ if (!theResource.getResourceMetadata().containsKey(ALLOW_STATUS_CHANGE)) {
+ throw new UnprocessableEntityException("Subscription.status can not be changed from " + existingStatus + " to " + status);
+ }
+ }
+ }
+
+ if (theEntityToSave.getId() == null) {
+ if (status != SubscriptionStatusEnum.REQUESTED) {
+ throw new UnprocessableEntityException("Subscription.status must be " + SubscriptionStatusEnum.REQUESTED.getCode() + " on a newly created subscription");
+ }
+ }
+
+ }
+
+}
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
index aa5e3361a1a..48a5374251a 100644
--- 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
@@ -60,7 +60,7 @@ public class FhirResourceDaoValueSetDstu2 extends FhirResourceDaoDstu2
if (sourceEntity == null) {
throw new ResourceNotFoundException(theId);
}
- ValueSet source = (ValueSet) toResource(sourceEntity);
+ ValueSet source = (ValueSet) toResource(sourceEntity, false);
/*
* Add composed concepts
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1.java
index 94be640520c..16f79a65a38 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu1.java
@@ -104,6 +104,7 @@ public class FhirSystemDaoDstu1 extends BaseHapiFhirSystemDao> {
OperationOutcome oo = new OperationOutcome();
retVal.add(oo);
+ Date updateTime = new Date();
for (int resourceIdx = 0; resourceIdx < theResources.size(); resourceIdx++) {
IResource nextResource = theResources.get(resourceIdx);
@@ -160,6 +161,8 @@ public class FhirSystemDaoDstu1 extends BaseHapiFhirSystemDao> {
if (entity == null) {
nextResouceOperationOut = BundleEntryTransactionMethodEnum.POST;
entity = toEntity(nextResource);
+ entity.setUpdated(updateTime);
+ entity.setPublished(updateTime);
if (nextId.isEmpty() == false && "cid:".equals(nextId.getBaseUrl())) {
ourLog.debug("Resource in transaction has ID[{}], will replace with server assigned ID", nextId.getIdPart());
} else if (nextResouceOperationIn == BundleEntryTransactionMethodEnum.POST) {
@@ -170,7 +173,7 @@ public class FhirSystemDaoDstu1 extends BaseHapiFhirSystemDao> {
if (candidateMatches.size() == 1) {
ourLog.debug("Resource with match URL [{}] already exists, will be NOOP", matchUrl);
BaseHasResource existingEntity = loadFirstEntityFromCandidateMatches(candidateMatches);
- IResource existing = (IResource) toResource(existingEntity);
+ IResource existing = (IResource) toResource(existingEntity, false);
persistedResources.add(null);
retVal.add(existing);
continue;
@@ -262,11 +265,11 @@ public class FhirSystemDaoDstu1 extends BaseHapiFhirSystemDao> {
InstantDt deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get(resource);
Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null;
if (deletedInstantOrNull == null && ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(resource) == BundleEntryTransactionMethodEnum.DELETE) {
- deletedTimestampOrNull = new Date();
+ deletedTimestampOrNull = updateTime;
ResourceMetadataKeyEnum.DELETED_AT.put(resource, new InstantDt(deletedTimestampOrNull));
}
- updateEntity(resource, table, table.getId() != null, deletedTimestampOrNull);
+ updateEntity(resource, table, table.getId() != null, deletedTimestampOrNull, updateTime);
}
long delay = System.currentTimeMillis() - start;
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java
index 126f175d6e3..27a72c16d14 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java
@@ -19,7 +19,9 @@ 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.defaultString;
+import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.Date;
import java.util.HashMap;
@@ -64,6 +66,8 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.FhirTerser;
+import ca.uhn.fhir.util.UrlUtil;
+import ca.uhn.fhir.util.UrlUtil.UrlParts;
public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSystemDaoDstu2.class);
@@ -92,10 +96,10 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
resp.addEntry().setResource(ooResp);
/*
- * For batch, we handle each entry as a mini-transaction in its own
- * database transaction so that if one fails, it doesn't prevent others
+ * For batch, we handle each entry as a mini-transaction in its own database transaction so that if one fails, it
+ * doesn't prevent others
*/
-
+
for (final Entry nextRequestEntry : theRequest.getEntry()) {
TransactionCallback callback = new TransactionCallback() {
@@ -118,13 +122,13 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
Entry subResponseEntry = nextResponseBundle.getEntry().get(0);
resp.addEntry(subResponseEntry);
/*
- * If the individual entry didn't have a resource in its response, bring the
- * sub-transaction's OperationOutcome across so the client can see it
+ * If the individual entry didn't have a resource in its response, bring the sub-transaction's
+ * OperationOutcome across so the client can see it
*/
if (subResponseEntry.getResource() == null) {
subResponseEntry.setResource(nextResponseBundle.getEntry().get(0).getResource());
}
-
+
} catch (BaseServerResponseException e) {
caughtEx = e;
} catch (Throwable t) {
@@ -167,75 +171,6 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
return retVal;
}
- private UrlParts parseUrl(String theAction, String theUrl) {
- UrlParts retVal = new UrlParts();
-
- //@formatter:off
- /*
- * We assume that the URL passed in is in one of the following forms:
- * [Resource Type]?[Search Params]
- * [Resource Type]/[Resource ID]
- * [Resource Type]/[Resource ID]/_history/[Version ID]
- */
- //@formatter:on
- int nextStart = 0;
- boolean nextIsHistory = false;
-
- for (int idx = 0; idx < theUrl.length(); idx++) {
- char nextChar = theUrl.charAt(idx);
- boolean atEnd = (idx + 1) == theUrl.length();
- if (nextChar == '?' || nextChar == '/' || atEnd) {
- int endIdx = atEnd ? idx + 1 : idx;
- String nextSubstring = theUrl.substring(nextStart, endIdx);
- if (retVal.getResourceType() == null) {
- retVal.setResourceType(nextSubstring);
- } else if (retVal.getResourceId() == null) {
- retVal.setResourceId(nextSubstring);
- } else if (nextIsHistory) {
- retVal.setVersionId(nextSubstring);
- } else {
- if (nextSubstring.equals(Constants.URL_TOKEN_HISTORY)) {
- nextIsHistory = true;
- } else {
- String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theAction, theUrl);
- throw new InvalidRequestException(msg);
- }
- }
- if (nextChar == '?') {
- if (theUrl.length() > idx + 1) {
- retVal.setParams(theUrl.substring(idx + 1, theUrl.length()));
- }
- break;
- }
- nextStart = idx + 1;
- }
- }
-
- RuntimeResourceDefinition resType;
- try {
- resType = getContext().getResourceDefinition(retVal.getResourceType());
- } catch (DataFormatException e) {
- String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theAction, theUrl);
- throw new InvalidRequestException(msg);
- }
- IFhirResourceDao extends IBaseResource> dao = null;
- if (resType != null) {
- dao = getDao(resType.getImplementingClass());
- }
- if (dao == null) {
- String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theAction, theUrl);
- throw new InvalidRequestException(msg);
- }
- retVal.setDao(dao);
-
- if (retVal.getResourceId() == null && retVal.getParams() == null) {
- String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theAction, theUrl);
- throw new InvalidRequestException(msg);
- }
-
- return retVal;
- }
-
@Transactional(propagation = Propagation.REQUIRED)
@Override
public Bundle transaction(Bundle theRequest) {
@@ -265,6 +200,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
ourLog.info("Beginning {} with {} resources", theActionName, theRequest.getEntry().size());
long start = System.currentTimeMillis();
+ Date updateTime = new Date();
Set allIds = new LinkedHashSet();
Map idSubstitutions = new HashMap();
@@ -275,11 +211,11 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
// TODO: process verbs in the correct order
for (int i = 0; i < theRequest.getEntry().size(); i++) {
-
+
if (i % 100 == 0) {
ourLog.info("Processed {} entries out of {}", i, theRequest.getEntry().size());
}
-
+
Entry nextEntry = theRequest.getEntry().get(i);
IResource res = nextEntry.getResource();
IdDt nextResourceId = null;
@@ -330,11 +266,12 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
// DELETE
Entry newEntry = response.addEntry();
String url = extractTransactionUrlOrThrowException(nextEntry, verb);
- UrlParts parts = parseUrl(verb.getCode(), url);
+ UrlParts parts = UrlUtil.parseUrl(url);
+ ca.uhn.fhir.jpa.dao.IFhirResourceDao extends IBaseResource> dao = toDao(parts, verb.getCode(), url);
if (parts.getResourceId() != null) {
- parts.getDao().delete(new IdDt(parts.getResourceType(), parts.getResourceId()));
+ dao.delete(new IdDt(parts.getResourceType(), parts.getResourceId()));
} else {
- parts.getDao().deleteByUrl(parts.getResourceType() + '?' + parts.getParams());
+ dao.deleteByUrl(parts.getResourceType() + '?' + parts.getParams());
}
newEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_204_NO_CONTENT));
@@ -350,7 +287,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
String url = extractTransactionUrlOrThrowException(nextEntry, verb);
- UrlParts parts = parseUrl(verb.getCode(), url);
+ UrlParts parts = UrlUtil.parseUrl(url);
if (isNotBlank(parts.getResourceId())) {
res.setId(new IdDt(parts.getResourceType(), parts.getResourceId()));
outcome = resourceDao.update(res, null, false);
@@ -365,10 +302,10 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
case GET: {
// SEARCH/READ/VREAD
String url = extractTransactionUrlOrThrowException(nextEntry, verb);
- UrlParts parts = parseUrl(verb.getCode(), url);
+ UrlParts parts = UrlUtil.parseUrl(url);
@SuppressWarnings("rawtypes")
- IFhirResourceDao resourceDao = parts.getDao();
+ IFhirResourceDao dao = toDao(parts, verb.getCode(), url);
String ifNoneMatch = nextEntry.getRequest().getIfNoneMatch();
if (isNotBlank(ifNoneMatch)) {
@@ -382,9 +319,9 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
if (isNotBlank(ifNoneMatch)) {
throw new InvalidRequestException("Unable to perform vread on '" + url + "' with ifNoneMatch also set. Do not include a version in the URL to perform a conditional read.");
}
- found = (IResource) resourceDao.read(new IdDt(parts.getResourceType(), parts.getResourceId(), parts.getVersionId()));
+ found = (IResource) dao.read(new IdDt(parts.getResourceType(), parts.getResourceId(), parts.getVersionId()));
} else {
- found = (IResource) resourceDao.read(new IdDt(parts.getResourceType(), parts.getResourceId()));
+ found = (IResource) dao.read(new IdDt(parts.getResourceType(), parts.getResourceId()));
if (isNotBlank(ifNoneMatch) && ifNoneMatch.equals(found.getId().getVersionIdPart())) {
notChanged = true;
}
@@ -402,9 +339,9 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
resp.setStatus(toStatusString(Constants.STATUS_HTTP_304_NOT_MODIFIED));
}
} else if (parts.getParams() != null) {
- RuntimeResourceDefinition def = getContext().getResourceDefinition(parts.getDao().getResourceType());
+ RuntimeResourceDefinition def = getContext().getResourceDefinition(dao.getResourceType());
SearchParameterMap params = translateMatchUrl(url, def);
- IBundleProvider bundle = parts.getDao().search(params);
+ IBundleProvider bundle = dao.search(params);
Bundle searchBundle = new Bundle();
searchBundle.setTotal(bundle.size());
@@ -453,7 +390,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
InstantDt deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get(nextResource);
Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null;
- updateEntity(nextResource, nextOutcome.getEntity(), false, deletedTimestampOrNull, true, false);
+ updateEntity(nextResource, nextOutcome.getEntity(), false, deletedTimestampOrNull, true, false, updateTime);
}
myEntityManager.flush();
@@ -468,8 +405,7 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
IFhirResourceDao> resourceDao = getDao(nextEntry.getResource().getClass());
Set val = resourceDao.processMatchUrl(matchUrl);
if (val.size() > 1) {
- throw new InvalidRequestException(
- "Unable to process " + theActionName + " - Request would cause multiple resources to match URL: \"" + matchUrl + "\". Does transaction request contain duplicates?");
+ throw new InvalidRequestException("Unable to process " + theActionName + " - Request would cause multiple resources to match URL: \"" + matchUrl + "\". Does transaction request contain duplicates?");
}
}
}
@@ -488,13 +424,38 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
long delay = System.currentTimeMillis() - start;
ourLog.info(theActionName + " completed in {}ms", new Object[] { delay });
-
+
notifyWriteCompleted();
response.setType(BundleTypeEnum.TRANSACTION_RESPONSE);
return response;
}
+ private ca.uhn.fhir.jpa.dao.IFhirResourceDao extends IBaseResource> toDao(UrlParts theParts, String theVerb, String theUrl) {
+ RuntimeResourceDefinition resType;
+ try {
+ resType = getContext().getResourceDefinition(theParts.getResourceType());
+ } catch (DataFormatException e) {
+ String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
+ throw new InvalidRequestException(msg);
+ }
+ IFhirResourceDao extends IBaseResource> dao = null;
+ if (resType != null) {
+ dao = getDao(resType.getImplementingClass());
+ }
+ if (dao == null) {
+ String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
+ throw new InvalidRequestException(msg);
+ }
+
+ if (theParts.getResourceId() == null && theParts.getParams() == null) {
+ String msg = getContext().getLocalizer().getMessage(BaseHapiFhirSystemDao.class, "transactionInvalidUrl", theVerb, theUrl);
+ throw new InvalidRequestException(msg);
+ }
+
+ return dao;
+ }
+
private IFhirResourceDao> getDaoOrThrowException(Class extends IResource> theClass) {
IFhirResourceDao extends IResource> retVal = getDao(theClass);
if (retVal == null) {
@@ -503,15 +464,15 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
return retVal;
}
- private static void handleTransactionCreateOrUpdateOutcome(Map idSubstitutions, Map idToPersistedOutcome, IdDt nextResourceId, DaoMethodOutcome outcome,
- Entry newEntry, String theResourceType, IResource theRes) {
+ private static void handleTransactionCreateOrUpdateOutcome(Map idSubstitutions, Map idToPersistedOutcome, IdDt nextResourceId, DaoMethodOutcome outcome, Entry newEntry, String theResourceType, IResource theRes) {
IdDt newId = (IdDt) outcome.getId().toUnqualifiedVersionless();
IdDt resourceId = isPlaceholder(nextResourceId) ? nextResourceId : nextResourceId.toUnqualifiedVersionless();
if (newId.equals(resourceId) == false) {
idSubstitutions.put(resourceId, newId);
if (isPlaceholder(resourceId)) {
/*
- * The correct way for substitution IDs to be is to be with no resource type, but we'll accept the qualified kind too just to be lenient.
+ * The correct way for substitution IDs to be is to be with no resource type, but we'll accept the qualified
+ * kind too just to be lenient.
*/
idSubstitutions.put(new IdDt(theResourceType + '/' + resourceId.getValue()), newId);
}
@@ -538,52 +499,4 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao {
return Integer.toString(theStatusCode) + " " + defaultString(Constants.HTTP_STATUS_NAMES.get(theStatusCode));
}
- private static class UrlParts {
- private IFhirResourceDao extends IBaseResource> myDao;
- private String myParams;
- private String myResourceId;
- private String myResourceType;
- private String myVersionId;
-
- public IFhirResourceDao extends IBaseResource> getDao() {
- return myDao;
- }
-
- public String getParams() {
- return myParams;
- }
-
- public String getResourceId() {
- return myResourceId;
- }
-
- public String getResourceType() {
- return myResourceType;
- }
-
- public String getVersionId() {
- return myVersionId;
- }
-
- public void setDao(IFhirResourceDao extends IBaseResource> theDao) {
- myDao = theDao;
- }
-
- public void setParams(String theParams) {
- myParams = theParams;
- }
-
- public void setResourceId(String theResourceId) {
- myResourceId = theResourceId;
- }
-
- public void setResourceType(String theResourceType) {
- myResourceType = theResourceType;
- }
-
- public void setVersionId(String theVersionId) {
- myVersionId = theVersionId;
- }
- }
-
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoSubscription.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoSubscription.java
new file mode 100644
index 00000000000..4700011b9b8
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoSubscription.java
@@ -0,0 +1,36 @@
+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 org.hl7.fhir.instance.model.api.IBaseResource;
+
+import ca.uhn.fhir.jpa.entity.SubscriptionTable;
+import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
+
+public interface IFhirResourceDaoSubscription extends IFhirResourceDao {
+
+ void pollForNewUndeliveredResources();
+
+ void setSubscriptionStatus(Long theResourceId, SubscriptionStatusEnum theStatus);
+
+ SubscriptionTable getSubscriptionByResourceId(long theSubscriptionResourceId);
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java
index c161109b2ec..62efd040b37 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/BaseHasResource.java
@@ -98,7 +98,11 @@ public abstract class BaseHasResource {
public abstract IdDt getIdDt();
public InstantDt getPublished() {
- return new InstantDt(myPublished);
+ if (myPublished != null) {
+ return new InstantDt(myPublished);
+ } else {
+ return null;
+ }
}
public byte[] getResource() {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionCandidateResource.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionCandidateResource.java
new file mode 100644
index 00000000000..02616288289
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionCandidateResource.java
@@ -0,0 +1,47 @@
+package ca.uhn.fhir.jpa.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "HFJ_SUBSCRIPTION_CAND_RES")
+public class SubscriptionCandidateResource {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.SEQUENCE)
+ @SequenceGenerator(name = "SEQ_SUBSCRIPTION_CAND_ID", sequenceName = "SEQ_SUBSCRIPTION_CAND_ID")
+ @Column(name = "PID", insertable = false, updatable = false)
+ private Long myId;
+
+ @ManyToOne(fetch = FetchType.EAGER)
+ @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID")
+ private ResourceTable myResource;
+
+ @Column(name = "RES_VERSION", nullable = false)
+ private long myResourceVersion;
+
+ public ResourceTable getResource() {
+ return myResource;
+ }
+
+ public long getResourceVersion() {
+ return myResourceVersion;
+ }
+
+ public void setResource(ResourceTable theResource) {
+ myResource = theResource;
+ }
+
+ public void setResourceVersion(long theResourceVersion) {
+ myResourceVersion = theResourceVersion;
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionFlaggedResource.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionFlaggedResource.java
new file mode 100644
index 00000000000..73a7ae2f679
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionFlaggedResource.java
@@ -0,0 +1,29 @@
+package ca.uhn.fhir.jpa.entity;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+@Entity
+@Table(name = "HFJ_SUBSCRIPTION_FLAG_RES")
+public class SubscriptionFlaggedResource {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.SEQUENCE)
+ @SequenceGenerator(name = "SEQ_SUBSCRIPTION_FLAG_ID", sequenceName = "SEQ_SUBSCRIPTION_FLAG_ID")
+ @Column(name = "PID", insertable = false, updatable = false)
+ private Long myId;
+
+ @Temporal(TemporalType.TIMESTAMP)
+ @Column(name = "CREATED", nullable = false)
+ private Date myCreated;
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionTable.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionTable.java
new file mode 100644
index 00000000000..e602038f1c1
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionTable.java
@@ -0,0 +1,112 @@
+package ca.uhn.fhir.jpa.entity;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.ForeignKey;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToOne;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.persistence.UniqueConstraint;
+
+import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
+
+//@formatter:off
+@Entity
+@Table(name = "HFJ_SUBSCRIPTION", uniqueConstraints= {
+ @UniqueConstraint(name="IDX_SUBS_RESID", columnNames= { "RES_ID" }),
+ @UniqueConstraint(name="IDX_SUBS_NEXTCHECK", columnNames= { "SUBSCRIPTION_STATUS", "NEXT_CHECK" })
+})
+@NamedQueries({
+ @NamedQuery(name="Q_HFJ_SUBSCRIPTION_SET_STATUS", query="UPDATE SubscriptionTable t SET t.myStatus = :status WHERE t.myResId = :res_id"),
+ @NamedQuery(name="Q_HFJ_SUBSCRIPTION_NEXT_CHECK", query="SELECT t FROM SubscriptionTable t WHERE t.myStatus = :status AND t.myNextCheck <= :next_check"),
+ @NamedQuery(name="Q_HFJ_SUBSCRIPTION_GET_BY_RES", query="SELECT t FROM SubscriptionTable t WHERE t.myResId = :res_id"),
+ @NamedQuery(name="Q_HFJ_SUBSCRIPTION_DELETE", query="DELETE FROM SubscriptionTable t WHERE t.myResId = :res_id"),
+})
+//@formatter:on
+public class SubscriptionTable {
+
+ @Column(name = "CHECK_INTERVAL", nullable = false)
+ private long myCheckInterval;
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.SEQUENCE)
+ @SequenceGenerator(name = "SEQ_SUBSCRIPTION_ID", sequenceName = "SEQ_SUBSCRIPTION_ID")
+ @Column(name = "PID", insertable = false, updatable = false)
+ private Long myId;
+
+ @Temporal(TemporalType.TIMESTAMP)
+ @Column(name = "NEXT_CHECK", nullable = false)
+ private Date myNextCheck;
+
+ @Temporal(TemporalType.TIMESTAMP)
+ @Column(name = "NEXT_CHECK_SINCE", nullable = false)
+ private Date myNextCheckSince;
+
+ @Column(name = "RES_ID", insertable = false, updatable = false)
+ private Long myResId;
+
+ @Column(name = "SUBSCRIPTION_STATUS", nullable = false, length = 20)
+ @Enumerated(EnumType.STRING)
+ private SubscriptionStatusEnum myStatus;
+
+ @OneToOne()
+ @JoinColumn(name = "RES_ID", insertable = true, updatable = false, referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_SUBSCRIPTION_RESOURCE_ID") )
+ private ResourceTable mySubscriptionResource;
+
+ public long getCheckInterval() {
+ return myCheckInterval;
+ }
+
+ public Long getId() {
+ return myId;
+ }
+
+ public Date getNextCheck() {
+ return myNextCheck;
+ }
+
+ public Date getNextCheckSince() {
+ return myNextCheckSince;
+ }
+
+ public SubscriptionStatusEnum getStatus() {
+ return myStatus;
+ }
+
+ public ResourceTable getSubscriptionResource() {
+ return mySubscriptionResource;
+ }
+
+ public void setCheckInterval(long theCheckInterval) {
+ myCheckInterval = theCheckInterval;
+ }
+
+ public void setNextCheck(Date theNextCheck) {
+ myNextCheck = theNextCheck;
+ }
+
+ public void setNextCheckSince(Date theNextCheckSince) {
+ myNextCheckSince = theNextCheckSince;
+ }
+
+ public void setStatus(SubscriptionStatusEnum theStatus) {
+ myStatus = theStatus;
+ }
+
+ public void setSubscriptionResource(ResourceTable theSubscriptionResource) {
+ mySubscriptionResource = theSubscriptionResource;
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaDstu2Test.java
index e72f4ce64ea..35e88788e3e 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaDstu2Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaDstu2Test.java
@@ -17,7 +17,6 @@ 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.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
@@ -38,6 +37,9 @@ import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.entity.ResourceLink;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import ca.uhn.fhir.jpa.entity.ResourceTag;
+import ca.uhn.fhir.jpa.entity.SubscriptionCandidateResource;
+import ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource;
+import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.jpa.entity.TagDefinition;
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
@@ -55,6 +57,7 @@ import ca.uhn.fhir.model.dstu2.resource.Practitioner;
import ca.uhn.fhir.model.dstu2.resource.Questionnaire;
import ca.uhn.fhir.model.dstu2.resource.QuestionnaireResponse;
import ca.uhn.fhir.model.dstu2.resource.StructureDefinition;
+import ca.uhn.fhir.model.dstu2.resource.Subscription;
import ca.uhn.fhir.model.dstu2.resource.Substance;
import ca.uhn.fhir.model.dstu2.resource.ValueSet;
import ca.uhn.fhir.parser.IParser;
@@ -66,7 +69,6 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
@ContextConfiguration(locations={
"classpath:hapi-fhir-server-resourceproviders-dstu2.xml",
"classpath:fhir-jpabase-spring-test-config.xml"})
-@TransactionConfiguration(defaultRollback=false)
//@formatter:on
public abstract class BaseJpaDstu2Test extends BaseJpaTest {
@@ -74,9 +76,6 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
@Qualifier("myConceptMapDaoDstu2")
protected IFhirResourceDao myConceptMapDao;
@Autowired
- @Qualifier("mySubstanceDaoDstu2")
- protected IFhirResourceDao mySubstanceDao;
- @Autowired
protected DaoConfig myDaoConfig;
@Autowired
@Qualifier("myDeviceDaoDstu2")
@@ -126,6 +125,12 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
@Qualifier("myStructureDefinitionDaoDstu2")
protected IFhirResourceDao myStructureDefinitionDao;
@Autowired
+ @Qualifier("mySubscriptionDaoDstu2")
+ protected IFhirResourceDaoSubscription mySubscriptionDao;
+ @Autowired
+ @Qualifier("mySubstanceDaoDstu2")
+ protected IFhirResourceDao mySubstanceDao;
+ @Autowired
@Qualifier("mySystemDaoDstu2")
protected IFhirSystemDao mySystemDao;
@Autowired
@@ -160,6 +165,13 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
return newJsonParser.parseResource(type, string);
}
+ public TransactionTemplate newTxTemplate() {
+ TransactionTemplate retVal = new TransactionTemplate(myTxManager);
+ retVal.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
+ retVal.afterPropertiesSet();
+ return retVal;
+ }
+
public static void purgeDatabase(final EntityManager entityManager, PlatformTransactionManager theTxManager) {
TransactionTemplate txTemplate = new TransactionTemplate(theTxManager);
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
@@ -174,6 +186,8 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
txTemplate.execute(new TransactionCallback() {
@Override
public Void doInTransaction(TransactionStatus theStatus) {
+ entityManager.createQuery("DELETE from " + SubscriptionCandidateResource.class.getSimpleName() + " d").executeUpdate();
+ entityManager.createQuery("DELETE from " + SubscriptionFlaggedResource.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ForcedId.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamDate.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceIndexedSearchParamNumber.class.getSimpleName() + " d").executeUpdate();
@@ -189,6 +203,7 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
txTemplate.execute(new TransactionCallback() {
@Override
public Void doInTransaction(TransactionStatus theStatus) {
+ entityManager.createQuery("DELETE from " + SubscriptionTable.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceHistoryTag.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + ResourceTag.class.getSimpleName() + " d").executeUpdate();
entityManager.createQuery("DELETE from " + TagDefinition.class.getSimpleName() + " d").executeUpdate();
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2SubscriptionTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2SubscriptionTest.java
new file mode 100644
index 00000000000..830ef005170
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2SubscriptionTest.java
@@ -0,0 +1,156 @@
+package ca.uhn.fhir.jpa.dao;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Set;
+
+import javax.persistence.TypedQuery;
+
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+
+import ca.uhn.fhir.jpa.entity.SubscriptionTable;
+import ca.uhn.fhir.model.dstu2.resource.Observation;
+import ca.uhn.fhir.model.dstu2.resource.Patient;
+import ca.uhn.fhir.model.dstu2.resource.Subscription;
+import ca.uhn.fhir.model.dstu2.valueset.ObservationStatusEnum;
+import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum;
+import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
+import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
+
+public class FhirResourceDaoDstu2SubscriptionTest extends BaseJpaDstu2Test {
+
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu2SubscriptionTest.class);
+
+ @Test
+ public void testCreateSubscriptionInvalidCriteria() {
+ Subscription subs = new Subscription();
+ subs.setCriteria("Observation");
+ try {
+ mySubscriptionDao.create(subs);
+ fail();
+ } catch (UnprocessableEntityException e) {
+ assertThat(e.getMessage(), containsString("Subscription.criteria must be in the form \"{Resource Type}?[params]\""));
+ }
+
+ subs = new Subscription();
+ subs.setCriteria("http://foo.com/Observation?AAA=BBB");
+ try {
+ mySubscriptionDao.create(subs);
+ fail();
+ } catch (UnprocessableEntityException e) {
+ assertThat(e.getMessage(), containsString("Subscription.criteria must be in the form \"{Resource Type}?[params]\""));
+ }
+
+ subs = new Subscription();
+ subs.setCriteria("ObservationZZZZ?a=b");
+ try {
+ mySubscriptionDao.create(subs);
+ fail();
+ } catch (UnprocessableEntityException e) {
+ assertThat(e.getMessage(), containsString("Subscription.criteria contains invalid/unsupported resource type: ObservationZZZZ"));
+ }
+
+ subs = new Subscription();
+ subs.setCriteria("Observation?identifier=123");
+ try {
+ mySubscriptionDao.create(subs);
+ fail();
+ } catch (UnprocessableEntityException e) {
+ assertThat(e.getMessage(), containsString("Subscription.channel.type must be populated on this server"));
+ }
+
+ subs = new Subscription();
+ subs.setCriteria("Observation?identifier=123");
+ subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
+ assertTrue(mySubscriptionDao.create(subs).getId().hasIdPart());
+
+ }
+
+ @Before
+ public void beforeEnableSubscription() {
+ myDaoConfig.setSubscriptionEnabled(true);
+ }
+
+ @Test
+ public void testSubscriptionResourcesAppear() {
+ String methodName = "testSubscriptionResourcesAppear";
+ Patient p = new Patient();
+ p.addName().addFamily(methodName);
+ IIdType pId = myPatientDao.create(p).getId().toUnqualifiedVersionless();
+
+ Observation obs = new Observation();
+ obs.getSubject().setReference(pId);
+ obs.setStatus(ObservationStatusEnum.FINAL);
+ IIdType beforeId = myObservationDao.create(obs).getId().toUnqualifiedVersionless();
+
+ Subscription subs = new Subscription();
+ subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
+ subs.setCriteria("Observation?subject=Patient/123");
+ IIdType id = mySubscriptionDao.create(subs).getId().toUnqualifiedVersionless();
+
+ obs = new Observation();
+ obs.getSubject().setReference(pId);
+ obs.setStatus(ObservationStatusEnum.FINAL);
+ IIdType afterId1 = myObservationDao.create(obs).getId().toUnqualifiedVersionless();
+
+ obs = new Observation();
+ obs.getSubject().setReference(pId);
+ obs.setStatus(ObservationStatusEnum.FINAL);
+ IIdType afterId2 = myObservationDao.create(obs).getId().toUnqualifiedVersionless();
+
+ mySubscriptionDao.pollForNewUndeliveredResources();
+ }
+
+ @Test
+ public void testCreateSubscription() {
+ Subscription subs = new Subscription();
+ subs.setCriteria("Observation?subject=Patient/123");
+ subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
+
+ IIdType id = mySubscriptionDao.create(subs).getId().toUnqualifiedVersionless();
+
+ TypedQuery q = myEntityManager.createQuery("SELECT t from SubscriptionTable t WHERE t.mySubscriptionResource.myId = :id", SubscriptionTable.class);
+ q.setParameter("id", id.getIdPartAsLong());
+ final SubscriptionTable table = q.getSingleResult();
+
+ assertNotNull(table);
+ assertNotNull(table.getNextCheck());
+ assertEquals(table.getNextCheck(), table.getSubscriptionResource().getPublished().getValue());
+ assertEquals(SubscriptionStatusEnum.REQUESTED, myEntityManager.find(SubscriptionTable.class, table.getId()).getStatus());
+ assertEquals(SubscriptionStatusEnum.REQUESTED, mySubscriptionDao.read(id).getStatusElement().getValueAsEnum());
+
+ mySubscriptionDao.setSubscriptionStatus(id.getIdPartAsLong(), SubscriptionStatusEnum.ACTIVE);
+
+ assertEquals(SubscriptionStatusEnum.ACTIVE, myEntityManager.find(SubscriptionTable.class, table.getId()).getStatus());
+ assertEquals(SubscriptionStatusEnum.ACTIVE, mySubscriptionDao.read(id).getStatusElement().getValueAsEnum());
+
+ mySubscriptionDao.delete(id);
+
+ assertNull(myEntityManager.find(SubscriptionTable.class, table.getId()));
+
+ /*
+ * Re-create again
+ */
+
+ subs = new Subscription();
+ subs.setCriteria("Observation?subject=Patient/123");
+ subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
+ subs.setId(id);
+ mySubscriptionDao.update(subs);
+
+ assertEquals(SubscriptionStatusEnum.REQUESTED, myEntityManager.createQuery("SELECT t FROM SubscriptionTable t WHERE t.myResId = " + id.getIdPart(), SubscriptionTable.class).getSingleResult().getStatus());
+ assertEquals(SubscriptionStatusEnum.REQUESTED, mySubscriptionDao.read(id).getStatusElement().getValueAsEnum());
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2Test.java
index 45b11e2f92a..8051448f839 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2Test.java
@@ -77,6 +77,7 @@ import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
+import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.SortOrderEnum;
@@ -966,8 +967,13 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
assertEquals(id.withVersion("1"), entries.get(2).getIdElement());
assertNull(ResourceMetadataKeyEnum.DELETED_AT.get((IResource) entries.get(0)));
+ assertEquals(BundleEntryTransactionMethodEnum.PUT, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get((IResource) entries.get(0)));
+
assertNotNull(ResourceMetadataKeyEnum.DELETED_AT.get((IResource) entries.get(1)));
+ assertEquals(BundleEntryTransactionMethodEnum.DELETE, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get((IResource) entries.get(1)));
+
assertNull(ResourceMetadataKeyEnum.DELETED_AT.get((IResource) entries.get(2)));
+ assertEquals(BundleEntryTransactionMethodEnum.POST, ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get((IResource) entries.get(2)));
}
@Test
diff --git a/hapi-fhir-jpaserver-base/src/test/resources/META-INF/persistence.xml b/hapi-fhir-jpaserver-base/src/test/resources/META-INF/persistence.xml
index f990e6c30cf..bad2178e79b 100644
--- a/hapi-fhir-jpaserver-base/src/test/resources/META-INF/persistence.xml
+++ b/hapi-fhir-jpaserver-base/src/test/resources/META-INF/persistence.xml
@@ -21,6 +21,9 @@
ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords
ca.uhn.fhir.jpa.entity.ResourceLink
ca.uhn.fhir.jpa.entity.ResourceTag
+ ca.uhn.fhir.jpa.entity.SubscriptionTable
+ ca.uhn.fhir.jpa.entity.SubscriptionCandidateResource
+ ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource
ca.uhn.fhir.jpa.entity.TagDefinition
false
diff --git a/hapi-fhir-jpaserver-example/src/main/resources/META-INF/fhirtest_persistence.xml b/hapi-fhir-jpaserver-example/src/main/resources/META-INF/fhirtest_persistence.xml
index 6ec380207c0..3b0edd509bd 100644
--- a/hapi-fhir-jpaserver-example/src/main/resources/META-INF/fhirtest_persistence.xml
+++ b/hapi-fhir-jpaserver-example/src/main/resources/META-INF/fhirtest_persistence.xml
@@ -20,6 +20,9 @@
ca.uhn.fhir.jpa.entity.ResourceLink
ca.uhn.fhir.jpa.entity.ResourceTable
ca.uhn.fhir.jpa.entity.ResourceTag
+ ca.uhn.fhir.jpa.entity.SubscriptionTable
+ ca.uhn.fhir.jpa.entity.SubscriptionCandidateResource
+ ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource
ca.uhn.fhir.jpa.entity.TagDefinition
true
diff --git a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/hapi-fhir-server-database-config.xml b/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/hapi-fhir-server-database-config.xml
index c2dafc4915c..6e9f1f4a0d0 100644
--- a/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/hapi-fhir-server-database-config.xml
+++ b/hapi-fhir-jpaserver-example/src/main/webapp/WEB-INF/hapi-fhir-server-database-config.xml
@@ -21,7 +21,7 @@
and other properties supported by BasicDataSource.
-->
-
+
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/META-INF/fhirtest_persistence.xml b/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/META-INF/fhirtest_persistence.xml
index a1d85bfa9c8..8fd4f7e4de4 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/META-INF/fhirtest_persistence.xml
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/META-INF/fhirtest_persistence.xml
@@ -20,6 +20,9 @@
ca.uhn.fhir.jpa.entity.ResourceLink
ca.uhn.fhir.jpa.entity.ResourceTable
ca.uhn.fhir.jpa.entity.ResourceTag
+ ca.uhn.fhir.jpa.entity.SubscriptionTable
+ ca.uhn.fhir.jpa.entity.SubscriptionCandidateResource
+ ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource
ca.uhn.fhir.jpa.entity.TagDefinition
true
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/test/resources/fhir_jpatest_persistence.xml b/hapi-fhir-jpaserver-uhnfhirtest/src/test/resources/fhir_jpatest_persistence.xml
index 56306183fa6..e655590260a 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/test/resources/fhir_jpatest_persistence.xml
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/test/resources/fhir_jpatest_persistence.xml
@@ -16,7 +16,10 @@
ca.uhn.fhir.jpa.entity.ResourceLink
ca.uhn.fhir.jpa.entity.ResourceTable
ca.uhn.fhir.jpa.entity.ResourceTag
-
+ ca.uhn.fhir.jpa.entity.SubscriptionTable
+ ca.uhn.fhir.jpa.entity.SubscriptionCandidateResource
+ ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource
+
true
diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml
index 78039287a3c..595ac70b4ed 100644
--- a/hapi-fhir-testpage-overlay/pom.xml
+++ b/hapi-fhir-testpage-overlay/pom.xml
@@ -104,12 +104,10 @@
org.springframework
spring-webmvc
- ${spring_version}
org.springframework
spring-context
- ${spring_version}
xml-apis
@@ -120,53 +118,44 @@
org.springframework
spring-beans
- ${spring_version}
org.springframework
spring-tx
- ${spring_version}
org.springframework
spring-context-support
- ${spring_version}
org.springframework
spring-web
- ${spring_version}
org.eclipse.jetty
jetty-servlets
- ${jetty_version}
test
org.eclipse.jetty
jetty-webapp
- ${jetty_version}
test
org.eclipse.jetty
jetty-server
- ${jetty_version}
test
org.eclipse.jetty
jetty-servlet
- ${jetty_version}
test
org.eclipse.jetty
jetty-util
- ${jetty_version}
test
diff --git a/hapi-fhir-testpage-overlay/src/test/resources/META-INF/persistence.xml b/hapi-fhir-testpage-overlay/src/test/resources/META-INF/persistence.xml
index 3dcefdcd452..c91d1098ab5 100644
--- a/hapi-fhir-testpage-overlay/src/test/resources/META-INF/persistence.xml
+++ b/hapi-fhir-testpage-overlay/src/test/resources/META-INF/persistence.xml
@@ -21,6 +21,9 @@
ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords
ca.uhn.fhir.jpa.entity.ResourceLink
ca.uhn.fhir.jpa.entity.ResourceTag
+ ca.uhn.fhir.jpa.entity.SubscriptionTable
+ ca.uhn.fhir.jpa.entity.SubscriptionCandidateResource
+ ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource
ca.uhn.fhir.jpa.entity.TagDefinition
false
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 af2b55a94a1..bb0fa08afcd 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
@@ -33,7 +33,7 @@
#foreach ( $res in $resources )
#else
class="ca.uhn.fhir.jpa.dao.FhirResourceDao${versionCapitalized}">
diff --git a/pom.xml b/pom.xml
index 574f0aa7cba..743cd97ebba 100644
--- a/pom.xml
+++ b/pom.xml
@@ -523,6 +523,11 @@
spring-web
${spring_version}
+
+ org.springframework
+ spring-webmvc
+ ${spring_version}
+
org.thymeleaf
thymeleaf
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 500eda8e512..55d25a4d84d 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -26,6 +26,10 @@
JPA server did not correctly index search parameters of type "reference" where the
path had multiple entries (i.e. "Resource.path1 | Resource.path2")
+
+ JPA server _history operations (server, type, instance) not correctly set the
+ Bundle.entry.request.method to POST or PUT for create and updates of the resource.
+
From f2118df9b87f64af37115fe8f688a610fedf5f7c Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Mon, 21 Sep 2015 21:29:04 -0400
Subject: [PATCH 05/13] FIx #225 - Support and/or in JPA on _id and _language
params
---
.../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 60 +++----
.../dao/FhirResourceDaoSubscriptionDstu2.java | 3 +
.../dao/FhirResourceDaoDstu2SearchTest.java | 155 +++++++++++++++++-
.../provider/ResourceProviderDstu2Test.java | 27 +++
.../resources/vm/jpa_resource_provider.vm | 4 +-
src/changes/changes.xml | 3 +
6 files changed, 219 insertions(+), 33 deletions(-)
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 f3baeae53c2..173eac50826 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
@@ -245,9 +245,10 @@ public abstract class BaseHapiFhirResourceDao extends BaseH
HashSet found = new HashSet(q.getResultList());
if (!theExistingPids.isEmpty()) {
theExistingPids.retainAll(found);
+ return theExistingPids;
+ } else {
+ return found;
}
-
- return found;
}
// private Set addPredicateComposite(String theParamName, Set thePids, List extends
@@ -2127,42 +2128,41 @@ public abstract class BaseHapiFhirResourceDao extends BaseH
if (nextParamEntry.getValue().isEmpty()) {
continue;
- } else if (nextParamEntry.getValue().size() > 1) {
- throw new InvalidRequestException("AND queries not supported for _id (Multiple instances of this param found)");
} else {
- Set joinPids = new HashSet();
- List extends IQueryParameterType> nextValue = nextParamEntry.getValue().get(0);
- if (nextValue == null || nextValue.size() == 0) {
- continue;
- } else {
- for (IQueryParameterType next : nextValue) {
- String value = next.getValueAsQueryToken();
- IIdType valueId = new IdDt(value);
+ for (List extends IQueryParameterType> nextValue : nextParamEntry.getValue()) {
+ Set joinPids = new HashSet();
+ if (nextValue == null || nextValue.size() == 0) {
+ continue;
+ } else {
+ for (IQueryParameterType next : nextValue) {
+ String value = next.getValueAsQueryToken();
+ IIdType valueId = new IdDt(value);
- try {
- BaseHasResource entity = readEntity(valueId);
- if (entity.getDeleted() != null) {
- continue;
+ try {
+ BaseHasResource entity = readEntity(valueId);
+ if (entity.getDeleted() != null) {
+ continue;
+ }
+ joinPids.add(entity.getId());
+ } catch (ResourceNotFoundException e) {
+ // This isn't an error, just means no result found
}
- joinPids.add(entity.getId());
- } catch (ResourceNotFoundException e) {
- // This isn't an error, just means no result found
+ }
+ if (joinPids.isEmpty()) {
+ return new HashSet();
}
}
- if (joinPids.isEmpty()) {
+
+ pids = addPredicateId(pids, joinPids);
+ if (pids.isEmpty()) {
return new HashSet();
}
- }
- pids = addPredicateId(pids, joinPids);
- if (pids.isEmpty()) {
- return new HashSet();
- }
-
- if (pids.isEmpty()) {
- pids.addAll(joinPids);
- } else {
- pids.retainAll(joinPids);
+ if (pids.isEmpty()) {
+ pids.addAll(joinPids);
+ } else {
+ pids.retainAll(joinPids);
+ }
}
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
index 32d72e2196f..20bcdf3a668 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
@@ -19,6 +19,7 @@ import org.springframework.transaction.annotation.Transactional;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.entity.ResourceTable;
+import ca.uhn.fhir.jpa.entity.SubscriptionCandidateResource;
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
@@ -76,6 +77,8 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2 q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_NEXT_CHECK", SubscriptionTable.class);
q.setParameter("next_check", new Date());
q.setParameter("status", SubscriptionStatusEnum.ACTIVE);
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2SearchTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2SearchTest.java
index 51b0a3eb090..8163f280542 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2SearchTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2SearchTest.java
@@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.dao;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsInRelativeOrder;
+import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.not;
@@ -68,6 +69,8 @@ import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.QuantityParam;
import ca.uhn.fhir.rest.param.ReferenceParam;
+import ca.uhn.fhir.rest.param.StringAndListParam;
+import ca.uhn.fhir.rest.param.StringOrListParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
@@ -297,6 +300,104 @@ public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test {
}
+ @Test
+ public void testSearchByIdParamWrongType() {
+ IIdType id1;
+ {
+ Patient patient = new Patient();
+ patient.addIdentifier().setSystem("urn:system").setValue("001");
+ id1 = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
+ }
+ IIdType id2;
+ {
+ Organization patient = new Organization();
+ patient.addIdentifier().setSystem("urn:system").setValue("001");
+ id2 = myOrganizationDao.create(patient).getId().toUnqualifiedVersionless();
+ }
+
+ SearchParameterMap params = new SearchParameterMap();
+ params.add("_id", new StringOrListParam().addOr(new StringParam(id1.getIdPart())).addOr(new StringParam(id2.getIdPart())));
+ assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1));
+
+ }
+
+ @Test
+ public void testSearchByIdParamOr() {
+ IIdType id1;
+ {
+ Patient patient = new Patient();
+ patient.addIdentifier().setSystem("urn:system").setValue("001");
+ id1 = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
+ }
+ IIdType id2;
+ {
+ Patient patient = new Patient();
+ patient.addIdentifier().setSystem("urn:system").setValue("001");
+ id2 = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
+ }
+
+ SearchParameterMap params = new SearchParameterMap();
+ params.add("_id", new StringOrListParam().addOr(new StringParam(id1.getIdPart())).addOr(new StringParam(id2.getIdPart())));
+ assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1, id2));
+
+ params = new SearchParameterMap();
+ params.add("_id", new StringOrListParam().addOr(new StringParam(id1.getIdPart())).addOr(new StringParam(id1.getIdPart())));
+ assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1));
+
+ params = new SearchParameterMap();
+ params.add("_id", new StringOrListParam().addOr(new StringParam(id1.getIdPart())).addOr(new StringParam("999999999999")));
+ assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1));
+
+ }
+
+ @Test
+ public void testSearchByIdParamAnd() {
+ IIdType id1;
+ {
+ Patient patient = new Patient();
+ patient.addIdentifier().setSystem("urn:system").setValue("001");
+ id1 = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
+ }
+ IIdType id2;
+ {
+ Patient patient = new Patient();
+ patient.addIdentifier().setSystem("urn:system").setValue("001");
+ id2 = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
+ }
+
+ SearchParameterMap params;
+ StringAndListParam param;
+
+ params = new SearchParameterMap();
+ param = new StringAndListParam();
+ param.addAnd(new StringOrListParam().addOr(new StringParam(id1.getIdPart())).addOr(new StringParam(id2.getIdPart())));
+ param.addAnd(new StringOrListParam().addOr(new StringParam(id1.getIdPart())));
+ params.add("_id", param);
+ assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1));
+
+ params = new SearchParameterMap();
+ param = new StringAndListParam();
+ param.addAnd(new StringOrListParam().addOr(new StringParam(id2.getIdPart())));
+ param.addAnd(new StringOrListParam().addOr(new StringParam(id1.getIdPart())));
+ params.add("_id", param);
+ assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), empty());
+
+ params = new SearchParameterMap();
+ param = new StringAndListParam();
+ param.addAnd(new StringOrListParam().addOr(new StringParam(id2.getIdPart())));
+ param.addAnd(new StringOrListParam().addOr(new StringParam("9999999999999")));
+ params.add("_id", param);
+ assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), empty());
+
+ params = new SearchParameterMap();
+ param = new StringAndListParam();
+ param.addAnd(new StringOrListParam().addOr(new StringParam("9999999999999")));
+ param.addAnd(new StringOrListParam().addOr(new StringParam(id2.getIdPart())));
+ params.add("_id", param);
+ assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), empty());
+
+ }
+
@Test
public void testSearchCompositeParam() {
Observation o1 = new Observation();
@@ -368,7 +469,6 @@ public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test {
assertEquals(0, retrieved.size());
}
}
-
@Test
public void testSearchLanguageParam() {
IIdType id1;
@@ -407,7 +507,60 @@ public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test {
List patients = toList(myPatientDao.search(params));
assertEquals(0, patients.size());
}
+ }
+ @Test
+ public void testSearchLanguageParamAndOr() {
+ IIdType id1;
+ {
+ Patient patient = new Patient();
+ patient.getLanguage().setValue("en_CA");
+ patient.addIdentifier().setSystem("urn:system").setValue("001");
+ patient.addName().addFamily("testSearchLanguageParam").addGiven("Joe");
+ id1 = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
+ }
+ IIdType id2;
+ {
+ Patient patient = new Patient();
+ patient.getLanguage().setValue("en_US");
+ patient.addIdentifier().setSystem("urn:system").setValue("002");
+ patient.addName().addFamily("testSearchLanguageParam").addGiven("John");
+ id2 = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
+ }
+ {
+ SearchParameterMap params = new SearchParameterMap();
+ params.add(Patient.SP_RES_LANGUAGE, new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("en_US")));
+ assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1, id2));
+ }
+ {
+ SearchParameterMap params = new SearchParameterMap();
+ params.add(Patient.SP_RES_LANGUAGE, new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ")));
+ assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1));
+ }
+ {
+ SearchParameterMap params = new SearchParameterMap();
+ StringAndListParam and = new StringAndListParam();
+ and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ")));
+ and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")));
+ params.add(Patient.SP_RES_LANGUAGE, and);
+ assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1));
+ }
+ {
+ SearchParameterMap params = new SearchParameterMap();
+ StringAndListParam and = new StringAndListParam();
+ and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ")));
+ and.addAnd(new StringOrListParam().addOr(new StringParam("ZZZZZ")));
+ params.add(Patient.SP_RES_LANGUAGE, and);
+ assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), empty());
+ }
+ {
+ SearchParameterMap params = new SearchParameterMap();
+ StringAndListParam and = new StringAndListParam();
+ and.addAnd(new StringOrListParam().addOr(new StringParam("ZZZZZ")));
+ and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ")));
+ params.add(Patient.SP_RES_LANGUAGE, and);
+ assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), empty());
+ }
}
@Test
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 bc760d1c938..b98fa6a2b08 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
@@ -151,6 +151,33 @@ public class ResourceProviderDstu2Test extends BaseJpaDstu2Test {
// }
// }
+ @Test
+ public void testSearchByIdOr() {
+ IIdType id1;
+ {
+ Patient patient = new Patient();
+ patient.addIdentifier().setSystem("urn:system").setValue("001");
+ id1 = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
+ }
+ IIdType id2;
+ {
+ Patient patient = new Patient();
+ patient.addIdentifier().setSystem("urn:system").setValue("001");
+ id2 = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
+ }
+
+ //@formatter:off
+ Bundle found = ourClient
+ .search()
+ .forResource(Patient.class)
+ .where(Patient.RES_ID.matches().values(id1.getIdPart(), id2.getIdPart()))
+ .and(Patient.RES_ID.matches().value(id1.getIdPart()))
+ .execute();
+ //@formatter:on
+
+ assertThat(toIdListUnqualifiedVersionless(found), containsInAnyOrder(id1));
+ }
+
@Test
public void testBundleCreate() throws Exception {
IGenericClient client = ourClient;
diff --git a/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm b/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm
index 3a99b727f5c..968cbab880a 100644
--- a/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm
+++ b/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm
@@ -44,11 +44,11 @@ public class ${className}ResourceProvider extends
@Description(shortDefinition="The resource identity")
@OptionalParam(name="_id")
- StringParam theId,
+ StringAndListParam theId,
@Description(shortDefinition="The resource language")
@OptionalParam(name="_language")
- StringParam theResourceLanguage,
+ StringAndListParam theResourceLanguage,
@Description(shortDefinition="Search for resources which have the given tag")
@OptionalParam(name=ca.uhn.fhir.rest.server.Constants.PARAM_TAG)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 55d25a4d84d..15be0c39bec 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -30,6 +30,9 @@
JPA server _history operations (server, type, instance) not correctly set the
Bundle.entry.request.method to POST or PUT for create and updates of the resource.
+
+ Support AND/OR on _id search parameter in JPA
+
From 36d8ed98d23e1d8351823a18ac547d517536310c Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Mon, 21 Sep 2015 21:37:50 -0400
Subject: [PATCH 06/13] Test to confirm #222 not an issue
---
.../dao/FhirResourceDaoDstu2SearchTest.java | 48 +++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2SearchTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2SearchTest.java
index 8163f280542..99b7e0bee3f 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2SearchTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2SearchTest.java
@@ -59,6 +59,7 @@ import ca.uhn.fhir.model.dstu2.resource.Practitioner;
import ca.uhn.fhir.model.dstu2.resource.Substance;
import ca.uhn.fhir.model.dstu2.resource.ValueSet;
import ca.uhn.fhir.model.dstu2.valueset.ContactPointSystemEnum;
+import ca.uhn.fhir.model.primitive.CodeDt;
import ca.uhn.fhir.model.primitive.DateDt;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.IdDt;
@@ -300,6 +301,53 @@ public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test {
}
+ /**
+ * #222
+ */
+ @Test
+ public void testSearchForDeleted() {
+
+ {
+ Patient patient = new Patient();
+ patient.setId("TEST");
+ patient.setLanguage(new CodeDt("TEST"));
+ patient.addName().addFamily("TEST");
+ patient.addIdentifier().setSystem("TEST").setValue("TEST");
+ myPatientDao.update(patient);
+ }
+
+ Map params = new HashMap();
+ params.put("_id", new StringDt("TEST"));
+ assertEquals(1, toList(myPatientDao.search(params)).size());
+
+ params.put("_language", new StringParam("TEST"));
+ assertEquals(1, toList(myPatientDao.search(params)).size());
+
+ params.put(Patient.SP_IDENTIFIER, new TokenParam("TEST", "TEST"));
+ assertEquals(1, toList(myPatientDao.search(params)).size());
+
+ params.put(Patient.SP_NAME, new StringParam("TEST"));
+ assertEquals(1, toList(myPatientDao.search(params)).size());
+
+ myPatientDao.delete(new IdDt("Patient/TEST"));
+
+ params = new HashMap();
+ params.put("_id", new StringDt("TEST"));
+ assertEquals(0, toList(myPatientDao.search(params)).size());
+
+ params.put("_language", new StringParam("TEST"));
+ assertEquals(0, toList(myPatientDao.search(params)).size());
+
+ params.put(Patient.SP_IDENTIFIER, new TokenParam("TEST", "TEST"));
+ assertEquals(0, toList(myPatientDao.search(params)).size());
+
+ params.put(Patient.SP_NAME, new StringParam("TEST"));
+ assertEquals(0, toList(myPatientDao.search(params)).size());
+
+
+ }
+
+
@Test
public void testSearchByIdParamWrongType() {
IIdType id1;
From ec8b3b68f6e3eb26af5ffbaa7ca57a8fdbcf3b9b Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Tue, 22 Sep 2015 08:06:23 -0400
Subject: [PATCH 07/13] Work on subscription
---
.../fhir/rest/param/ResourceParameter.java | 14 +-
.../interceptor/IServerInterceptor.java | 7 +-
.../interceptor/InterceptorAdapter.java | 30 ++---
.../java/ca/uhn/fhir/jpa/dao/DaoConfig.java | 7 +-
.../dao/FhirResourceDaoSubscriptionDstu2.java | 67 +---------
.../jpa/dao/IFhirResourceDaoSubscription.java | 7 -
.../provider/JpaResourceProviderDstu2.java | 15 ---
...onsRequireManualActivationInterceptor.java | 103 +++++++++++++++
.../FhirResourceDaoDstu2SubscriptionTest.java | 9 +-
.../BaseResourceProviderDstu2Test.java | 124 ++++++++++++++++++
.../provider/ResourceProviderDstu2Test.java | 120 +++--------------
...equireManualActivationInterceptorTest.java | 101 ++++++++++++++
.../WEB-INF/hapi-fhir-server-config.xml | 3 +
13 files changed, 386 insertions(+), 221 deletions(-)
create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptor.java
create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java
create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SubscriptionsRequireManualActivationInterceptorTest.java
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java
index eb559a9227d..c4e941218e6 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java
@@ -46,6 +46,7 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.parser.IParser;
+import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.IParameter;
import ca.uhn.fhir.rest.method.MethodUtil;
@@ -145,12 +146,15 @@ public class ResourceParameter implements IParameter {
return charset;
}
- public static IBaseResource loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding> theMethodBinding, Class extends IBaseResource> theResourceType) {
+ @SuppressWarnings("unchecked")
+ public static T loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding> theMethodBinding, Class theResourceType) {
FhirContext ctx = theRequest.getServer().getFhirContext();
final Charset charset = determineRequestCharset(theRequest);
Reader requestReader = createRequestReader(theRequest.getRawRequest(), charset);
+ RestOperationTypeEnum restOperationType = theMethodBinding != null ? theMethodBinding.getRestOperationType() : null;
+
EncodingEnum encoding = RestfulServerUtils.determineRequestEncodingNoDefault(theRequest);
if (encoding == null) {
String ctValue = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE);
@@ -173,24 +177,24 @@ public class ResourceParameter implements IParameter {
}
encoding = MethodUtil.detectEncodingNoDefault(body);
if (encoding == null) {
- String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "noContentTypeInRequest", theMethodBinding.getRestOperationType());
+ String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "noContentTypeInRequest", restOperationType);
throw new InvalidRequestException(msg);
} else {
requestReader = new InputStreamReader(new ByteArrayInputStream(theRequest.getRawRequest()), charset);
}
} else {
- String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getRestOperationType());
+ String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, restOperationType);
throw new InvalidRequestException(msg);
}
}
IParser parser = encoding.newParser(ctx);
- IBaseResource retVal;
+ T retVal;
if (theResourceType != null) {
retVal = parser.parseResource(theResourceType, requestReader);
} else {
- retVal = parser.parseResource(requestReader);
+ retVal = (T) parser.parseResource(requestReader);
}
if (theRequest.getId() != null && theRequest.getId().hasIdPart()) {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java
index 0e169e590be..5e0e495b556 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java
@@ -106,10 +106,11 @@ public interface IServerInterceptor {
* The incoming servlet request as provided by the servlet container
* @param theOperation
* The type of operation that the FHIR server has determined that the client is trying to invoke
- * @param theRequestDetails
- * An object which will be populated with any relevant details about the incoming request (this includes the HttpServletRequest)
+ * @param theProcessedRequest
+ * An object which will be populated with the details which were extracted from the raw request by the server,
+ * e.g. the FHIR operation type and the parsed resource body (if any).
*/
- void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theRequestDetails);
+ void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theProcessedRequest);
/**
* This method is called before any other processing takes place for each incoming request. It may be used to provide alternate handling for some requests, or to screen requests before they are
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.java
index 997d64230b9..b3a21db983c 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.java
@@ -42,7 +42,8 @@ import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
public class InterceptorAdapter implements IServerInterceptor {
@Override
- public boolean incomingRequestPreProcessed(HttpServletRequest theRequest, HttpServletResponse theResponse) {
+ public boolean handleException(RequestDetails theRequestDetails, BaseServerResponseException theException, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws ServletException,
+ IOException {
return true;
}
@@ -52,7 +53,12 @@ public class InterceptorAdapter implements IServerInterceptor {
}
@Override
- public boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
+ public void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theProcessedRequest) {
+ // nothing
+ }
+
+ @Override
+ public boolean incomingRequestPreProcessed(HttpServletRequest theRequest, HttpServletResponse theResponse) {
return true;
}
@@ -62,12 +68,17 @@ public class InterceptorAdapter implements IServerInterceptor {
}
@Override
- public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
+ public boolean outgoingResponse(RequestDetails theRequestDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
return true;
}
@Override
- public boolean outgoingResponse(RequestDetails theRequestDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
+ public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
+ return true;
+ }
+
+ @Override
+ public boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
return true;
}
@@ -75,16 +86,5 @@ public class InterceptorAdapter implements IServerInterceptor {
public BaseServerResponseException preProcessOutgoingException(RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theServletRequest) throws ServletException {
return null;
}
-
- @Override
- public boolean handleException(RequestDetails theRequestDetails, BaseServerResponseException theException, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws ServletException,
- IOException {
- return true;
- }
-
- @Override
- public void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theRequestDetails) {
- // nothing
- }
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
index 62e67396aae..2d8b384ae33 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
@@ -99,10 +99,9 @@ public class DaoConfig {
*
*/
public void setInterceptors(IServerInterceptor... theInterceptor) {
- if (theInterceptor == null || theInterceptor.length==0){
- setInterceptors(new ArrayList());
- } else {
- setInterceptors(Arrays.asList(theInterceptor));
+ setInterceptors(new ArrayList());
+ if (theInterceptor != null && theInterceptor.length != 0) {
+ getInterceptors().addAll(Arrays.asList(theInterceptor));
}
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
index 20bcdf3a668..21132614b95 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
@@ -4,7 +4,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
import java.util.Date;
import java.util.List;
-import java.util.Set;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
@@ -12,14 +11,12 @@ import javax.persistence.TypedQuery;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
-import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.entity.ResourceTable;
-import ca.uhn.fhir.jpa.entity.SubscriptionCandidateResource;
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
@@ -27,27 +24,10 @@ import ca.uhn.fhir.model.dstu2.resource.Subscription;
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.DataFormatException;
-import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
-import ca.uhn.fhir.util.UrlUtil;
-import ca.uhn.fhir.util.UrlUtil.UrlParts;
public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2implements IFhirResourceDaoSubscription {
- private static final ResourceMetadataKeyEnum ALLOW_STATUS_CHANGE = new ResourceMetadataKeyEnum(FhirResourceDaoSubscriptionDstu2.class.getName() + "_ALLOW_STATUS_CHANGE") {
- private static final long serialVersionUID = 1;
-
- @Override
- public Object get(IResource theResource) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void put(IResource theResource, Object theObject) {
- throw new UnsupportedOperationException();
- }
- };
-
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSubscriptionDstu2.class);
private void createSubscriptionTable(ResourceTable theEntity, Subscription theSubscription) {
@@ -59,15 +39,6 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2 q = myEntityManager.createNamedQuery("Q_HFJ_SUBSCRIPTION_GET_BY_RES", SubscriptionTable.class);
- q.setParameter("res_id", theSubscriptionResourceId);
- return q.getSingleResult();
- }
-
-
-
@Scheduled(fixedDelay = 10 * DateUtils.MILLIS_PER_SECOND)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
@@ -94,20 +65,6 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2 extends IFhirResourceDao {
void pollForNewUndeliveredResources();
- void setSubscriptionStatus(Long theResourceId, SubscriptionStatusEnum theStatus);
-
- SubscriptionTable getSubscriptionByResourceId(long theSubscriptionResourceId);
-
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java
index 4f52d6826de..cbaa640b52f 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java
@@ -24,16 +24,9 @@ import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.model.api.IResource;
-import ca.uhn.fhir.model.base.resource.BaseOperationOutcome.BaseIssue;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
-import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
-import ca.uhn.fhir.model.dstu2.resource.OperationOutcome.Issue;
import ca.uhn.fhir.model.dstu2.resource.Parameters;
-import ca.uhn.fhir.model.dstu2.valueset.IssueSeverityEnum;
-import ca.uhn.fhir.model.dstu2.valueset.IssueTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt;
-import ca.uhn.fhir.parser.IParser;
-import ca.uhn.fhir.parser.IParserErrorHandler;
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete;
@@ -46,16 +39,12 @@ import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.server.EncodingEnum;
-import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
-import ca.uhn.fhir.validation.FhirValidator;
-import ca.uhn.fhir.validation.ValidationResult;
public class JpaResourceProviderDstu2 extends BaseJpaResourceProvider {
public static final String OPERATION_NAME_META = "$meta";
public static final String OPERATION_NAME_META_DELETE = "$meta-delete";
public static final String OPERATION_NAME_META_ADD = "$meta-add";
- private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(JpaResourceProviderDstu2.class);
public JpaResourceProviderDstu2() {
// nothing
@@ -150,10 +139,6 @@ public class JpaResourceProviderDstu2 extends BaseJpaResour
theResource.setId(theId);
return getDao().update(theResource);
}
- } catch (ResourceNotFoundException e) {
- ourLog.info("Can't update resource with ID[" + theId.getValue() + "] because it doesn't exist, going to create it instead");
- theResource.setId(theId);
- return getDao().create(theResource);
} finally {
endRequest(theRequest);
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptor.java
new file mode 100644
index 00000000000..ba2289bfd1a
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptor.java
@@ -0,0 +1,103 @@
+package ca.uhn.fhir.jpa.util;
+
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+
+import ca.uhn.fhir.jpa.dao.FhirResourceDaoSubscriptionDstu2;
+import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
+import ca.uhn.fhir.model.api.IResource;
+import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
+import ca.uhn.fhir.model.dstu2.resource.Subscription;
+import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
+import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
+import ca.uhn.fhir.rest.method.RequestDetails;
+import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
+import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
+import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
+import net.sourceforge.cobertura.CoverageIgnore;
+
+/**
+ * Interceptor which requires newly created {@link Subscription subscriptions} to be in
+ * {@link SubscriptionStatusEnum#REQUESTED} state and prevents clients from changing the status.
+ */
+public class SubscriptionsRequireManualActivationInterceptor extends InterceptorAdapter {
+
+ public static final ResourceMetadataKeyEnum ALLOW_STATUS_CHANGE = new ResourceMetadataKeyEnum(FhirResourceDaoSubscriptionDstu2.class.getName() + "_ALLOW_STATUS_CHANGE") {
+ private static final long serialVersionUID = 1;
+
+ @CoverageIgnore
+ @Override
+ public Object get(IResource theResource) {
+ throw new UnsupportedOperationException();
+ }
+
+ @CoverageIgnore
+ @Override
+ public void put(IResource theResource, Object theObject) {
+ throw new UnsupportedOperationException();
+ }
+ };
+
+ @Autowired
+ @Qualifier("mySubscriptionDaoDstu2")
+ private IFhirResourceDao myDao;
+
+ @Override
+ public void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theProcessedRequest) {
+ switch (theOperation) {
+ case CREATE:
+ case UPDATE:
+ if (theProcessedRequest.getResourceType().equals("Subscription")) {
+ verifyStatusOk(theProcessedRequest);
+ }
+ default:
+ break;
+ }
+ }
+
+ public void setDao(IFhirResourceDao theDao) {
+ myDao = theDao;
+ }
+
+ private void verifyStatusOk(ActionRequestDetails theRequestDetails) {
+ Subscription subscription = (Subscription) theRequestDetails.getResource();
+ ;
+ SubscriptionStatusEnum newStatus = subscription.getStatusElement().getValueAsEnum();
+
+ if (newStatus == SubscriptionStatusEnum.REQUESTED || newStatus == SubscriptionStatusEnum.OFF) {
+ return;
+ }
+
+ IIdType requestId = theRequestDetails.getId();
+ if (requestId != null && requestId.hasIdPart()) {
+ Subscription existing;
+ try {
+ existing = myDao.read(requestId);
+ SubscriptionStatusEnum existingStatus = existing.getStatusElement().getValueAsEnum();
+ if (existingStatus != newStatus) {
+ throw new UnprocessableEntityException("Subscription.status can not be changed from " + describeStatus(existingStatus) + " to " + describeStatus(newStatus));
+ }
+ } catch (ResourceNotFoundException e) {
+ if (newStatus != SubscriptionStatusEnum.REQUESTED) {
+ throw new UnprocessableEntityException("Subscription.status must be '" + SubscriptionStatusEnum.REQUESTED.getCode() + "' on a newly created subscription");
+ }
+ }
+ } else {
+ if (newStatus != SubscriptionStatusEnum.REQUESTED) {
+ throw new UnprocessableEntityException("Subscription.status must be '" + SubscriptionStatusEnum.REQUESTED.getCode() + "' on a newly created subscription");
+ }
+ }
+ }
+
+ private String describeStatus(SubscriptionStatusEnum existingStatus) {
+ String existingStatusString;
+ if (existingStatus != null) {
+ existingStatusString = '\'' + existingStatus.getCode() + '\'';
+ } else {
+ existingStatusString = "null";
+ }
+ return existingStatusString;
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2SubscriptionTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2SubscriptionTest.java
index 830ef005170..d29b8e52745 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2SubscriptionTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2SubscriptionTest.java
@@ -35,6 +35,7 @@ public class FhirResourceDaoDstu2SubscriptionTest extends BaseJpaDstu2Test {
@Test
public void testCreateSubscriptionInvalidCriteria() {
Subscription subs = new Subscription();
+ subs.setStatus(SubscriptionStatusEnum.REQUESTED);
subs.setCriteria("Observation");
try {
mySubscriptionDao.create(subs);
@@ -44,6 +45,7 @@ public class FhirResourceDaoDstu2SubscriptionTest extends BaseJpaDstu2Test {
}
subs = new Subscription();
+ subs.setStatus(SubscriptionStatusEnum.REQUESTED);
subs.setCriteria("http://foo.com/Observation?AAA=BBB");
try {
mySubscriptionDao.create(subs);
@@ -53,6 +55,7 @@ public class FhirResourceDaoDstu2SubscriptionTest extends BaseJpaDstu2Test {
}
subs = new Subscription();
+ subs.setStatus(SubscriptionStatusEnum.REQUESTED);
subs.setCriteria("ObservationZZZZ?a=b");
try {
mySubscriptionDao.create(subs);
@@ -62,6 +65,7 @@ public class FhirResourceDaoDstu2SubscriptionTest extends BaseJpaDstu2Test {
}
subs = new Subscription();
+ subs.setStatus(SubscriptionStatusEnum.REQUESTED);
subs.setCriteria("Observation?identifier=123");
try {
mySubscriptionDao.create(subs);
@@ -71,6 +75,7 @@ public class FhirResourceDaoDstu2SubscriptionTest extends BaseJpaDstu2Test {
}
subs = new Subscription();
+ subs.setStatus(SubscriptionStatusEnum.REQUESTED);
subs.setCriteria("Observation?identifier=123");
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
assertTrue(mySubscriptionDao.create(subs).getId().hasIdPart());
@@ -117,6 +122,7 @@ public class FhirResourceDaoDstu2SubscriptionTest extends BaseJpaDstu2Test {
Subscription subs = new Subscription();
subs.setCriteria("Observation?subject=Patient/123");
subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
+ subs.setStatus(SubscriptionStatusEnum.REQUESTED);
IIdType id = mySubscriptionDao.create(subs).getId().toUnqualifiedVersionless();
@@ -130,7 +136,8 @@ public class FhirResourceDaoDstu2SubscriptionTest extends BaseJpaDstu2Test {
assertEquals(SubscriptionStatusEnum.REQUESTED, myEntityManager.find(SubscriptionTable.class, table.getId()).getStatus());
assertEquals(SubscriptionStatusEnum.REQUESTED, mySubscriptionDao.read(id).getStatusElement().getValueAsEnum());
- mySubscriptionDao.setSubscriptionStatus(id.getIdPartAsLong(), SubscriptionStatusEnum.ACTIVE);
+ subs.setStatus(SubscriptionStatusEnum.ACTIVE);
+ mySubscriptionDao.update(subs);
assertEquals(SubscriptionStatusEnum.ACTIVE, myEntityManager.find(SubscriptionTable.class, table.getId()).getStatus());
assertEquals(SubscriptionStatusEnum.ACTIVE, mySubscriptionDao.read(id).getStatusElement().getValueAsEnum());
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java
new file mode 100644
index 00000000000..c8ce3021f72
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java
@@ -0,0 +1,124 @@
+package ca.uhn.fhir.jpa.provider;
+
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+
+import ca.uhn.fhir.jpa.dao.BaseJpaDstu2Test;
+import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
+import ca.uhn.fhir.model.api.Bundle;
+import ca.uhn.fhir.model.api.BundleEntry;
+import ca.uhn.fhir.model.dstu2.resource.Patient;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
+import ca.uhn.fhir.rest.client.IGenericClient;
+import ca.uhn.fhir.rest.client.ServerValidationModeEnum;
+import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
+import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
+import ca.uhn.fhir.rest.server.RestfulServer;
+
+public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
+
+ protected static IGenericClient ourClient;
+ protected static CloseableHttpClient ourHttpClient;
+ protected static int ourPort;
+ private static Server ourServer;
+ protected static String ourServerBase;
+
+ public BaseResourceProviderDstu2Test() {
+ super();
+ }
+
+ protected List toIdListUnqualifiedVersionless(Bundle found) {
+ List list = new ArrayList();
+ for (BundleEntry next : found.getEntries()) {
+ list.add(next.getResource().getId().toUnqualifiedVersionless());
+ }
+ return list;
+ }
+
+ protected List toNameList(Bundle resp) {
+ List names = new ArrayList();
+ for (BundleEntry next : resp.getEntries()) {
+ Patient nextPt = (Patient) next.getResource();
+ String nextStr = nextPt.getNameFirstRep().getGivenAsSingleString() + " " + nextPt.getNameFirstRep().getFamilyAsSingleString();
+ if (isNotBlank(nextStr)) {
+ names.add(nextStr);
+ }
+ }
+ return names;
+ }
+
+ @AfterClass
+ public static void afterClass() throws Exception {
+ ourServer.stop();
+ ourHttpClient.close();
+ }
+
+ @After
+ public void after() {
+ myFhirCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE);
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Before
+ public void before() throws Exception {
+ myFhirCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
+ myFhirCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
+
+ if (ourServer == null) {
+ ourPort = RandomServerPortProvider.findFreePort();
+
+ RestfulServer restServer = new RestfulServer(myFhirCtx);
+
+ ourServerBase = "http://localhost:" + ourPort + "/fhir/context";
+
+ restServer.setResourceProviders((List)myResourceProviders);
+
+ restServer.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
+
+ restServer.setPlainProviders(mySystemProvider);
+
+ JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(restServer, mySystemDao);
+ confProvider.setImplementationDescription("THIS IS THE DESC");
+ restServer.setServerConformanceProvider(confProvider);
+
+ restServer.setPagingProvider(new FifoMemoryPagingProvider(10));
+
+ Server server = new Server(ourPort);
+
+ ServletContextHandler proxyHandler = new ServletContextHandler();
+ proxyHandler.setContextPath("/");
+
+ ServletHolder servletHolder = new ServletHolder();
+ servletHolder.setServlet(restServer);
+ proxyHandler.addServlet(servletHolder, "/fhir/context/*");
+
+ server.setHandler(proxyHandler);
+ server.start();
+
+ ourClient = myFhirCtx.newRestfulGenericClient(ourServerBase);
+ ourClient.registerInterceptor(new LoggingInterceptor(true));
+
+ PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
+ HttpClientBuilder builder = HttpClientBuilder.create();
+ builder.setConnectionManager(connectionManager);
+ ourHttpClient = builder.build();
+
+ ourServer = server;
+ }
+ }
+
+}
\ No newline at end of file
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 b98fa6a2b08..7b76e5c81d1 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
@@ -1,6 +1,5 @@
package ca.uhn.fhir.jpa.provider;
-import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsInRelativeOrder;
@@ -29,7 +28,6 @@ import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.TimeUnit;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
@@ -41,23 +39,11 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClientBuilder;
-import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
import org.junit.Test;
-import ca.uhn.fhir.jpa.dao.BaseJpaDstu2Test;
-import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.model.api.Bundle;
-import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
@@ -95,27 +81,18 @@ import ca.uhn.fhir.model.primitive.UnsignedIntDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
-import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.client.IGenericClient;
-import ca.uhn.fhir.rest.client.ServerValidationModeEnum;
-import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.server.Constants;
-import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
-import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
-public class ResourceProviderDstu2Test extends BaseJpaDstu2Test {
+public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
- private static IGenericClient ourClient;
- private static CloseableHttpClient ourHttpClient;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderDstu2Test.class);
- private static int ourPort;
- private static Server ourServer;
- private static String ourServerBase;
+
// private static JpaConformanceProvider ourConfProvider;
@@ -229,6 +206,19 @@ public class ResourceProviderDstu2Test extends BaseJpaDstu2Test {
}
+ @Test
+ public void testCreateWithForcedId() throws IOException {
+ String methodName = "testCreateWithForcedId";
+
+ Patient p = new Patient();
+ p.addName().addFamily(methodName);
+ p.setId(methodName);
+
+ IIdType optId = ourClient.update().resource(p).execute().getId();
+ assertEquals(methodName, optId.getIdPart());
+ assertEquals("1", optId.getVersionIdPart());
+ }
+
@Test
public void testCreateQuestionnaireResponseWithValidation() throws IOException {
ValueSet options = new ValueSet();
@@ -1662,84 +1652,4 @@ public class ResourceProviderDstu2Test extends BaseJpaDstu2Test {
}
- private List toIdListUnqualifiedVersionless(Bundle found) {
- List list = new ArrayList();
- for (BundleEntry next : found.getEntries()) {
- list.add(next.getResource().getId().toUnqualifiedVersionless());
- }
- return list;
- }
-
- private List toNameList(Bundle resp) {
- List names = new ArrayList();
- for (BundleEntry next : resp.getEntries()) {
- Patient nextPt = (Patient) next.getResource();
- String nextStr = nextPt.getNameFirstRep().getGivenAsSingleString() + " " + nextPt.getNameFirstRep().getFamilyAsSingleString();
- if (isNotBlank(nextStr)) {
- names.add(nextStr);
- }
- }
- return names;
- }
-
- @AfterClass
- public static void afterClass() throws Exception {
- ourServer.stop();
- ourHttpClient.close();
- }
-
- @After
- public void after() {
- myFhirCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE);
- }
-
- @SuppressWarnings({ "unchecked", "rawtypes" })
- @Before
- public void before() throws Exception {
- myFhirCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
- myFhirCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
-
- if (ourServer == null) {
- ourPort = RandomServerPortProvider.findFreePort();
-
- RestfulServer restServer = new RestfulServer(myFhirCtx);
-
- ourServerBase = "http://localhost:" + ourPort + "/fhir/context";
-
- restServer.setResourceProviders((List)myResourceProviders);
-
- restServer.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
-
- restServer.setPlainProviders(mySystemProvider);
-
- JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(restServer, mySystemDao);
- confProvider.setImplementationDescription("THIS IS THE DESC");
- restServer.setServerConformanceProvider(confProvider);
-
- restServer.setPagingProvider(new FifoMemoryPagingProvider(10));
-
- Server server = new Server(ourPort);
-
- ServletContextHandler proxyHandler = new ServletContextHandler();
- proxyHandler.setContextPath("/");
-
- ServletHolder servletHolder = new ServletHolder();
- servletHolder.setServlet(restServer);
- proxyHandler.addServlet(servletHolder, "/fhir/context/*");
-
- server.setHandler(proxyHandler);
- server.start();
-
- ourClient = myFhirCtx.newRestfulGenericClient(ourServerBase);
- ourClient.registerInterceptor(new LoggingInterceptor(true));
-
- PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
- HttpClientBuilder builder = HttpClientBuilder.create();
- builder.setConnectionManager(connectionManager);
- ourHttpClient = builder.build();
-
- ourServer = server;
- }
- }
-
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SubscriptionsRequireManualActivationInterceptorTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SubscriptionsRequireManualActivationInterceptorTest.java
new file mode 100644
index 00000000000..c9fbd966018
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/SubscriptionsRequireManualActivationInterceptorTest.java
@@ -0,0 +1,101 @@
+package ca.uhn.fhir.jpa.provider;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.junit.Test;
+
+import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptor;
+import ca.uhn.fhir.model.dstu2.resource.Subscription;
+import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum;
+import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
+import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
+
+public class SubscriptionsRequireManualActivationInterceptorTest extends BaseResourceProviderDstu2Test {
+
+ @Test
+ public void testCreateInvalidNoStatus() {
+ Subscription subs = new Subscription();
+ subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
+ subs.setCriteria("Observation?identifier=123");
+ try {
+ ourClient.create().resource(subs).execute();
+ fail();
+ } catch (UnprocessableEntityException e) {
+ assertEquals("HTTP 422 Unprocessable Entity: Subscription.status must be 'requested' on a newly created subscription", e.getMessage());
+ }
+
+ subs.setId("ABC");
+ try {
+ ourClient.update().resource(subs).execute();
+ fail();
+ } catch (UnprocessableEntityException e) {
+ assertEquals("HTTP 422 Unprocessable Entity: Subscription.status must be 'requested' on a newly created subscription", e.getMessage());
+ }
+
+ subs.setStatus(SubscriptionStatusEnum.REQUESTED);
+ ourClient.update().resource(subs).execute();
+ }
+
+ @Test
+ public void testCreateInvalidWrongStatus() {
+ Subscription subs = new Subscription();
+ subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
+ subs.setStatus(SubscriptionStatusEnum.ACTIVE);
+ subs.setCriteria("Observation?identifier=123");
+ try {
+ ourClient.create().resource(subs).execute();
+ fail();
+ } catch (UnprocessableEntityException e) {
+ assertEquals("HTTP 422 Unprocessable Entity: Subscription.status must be 'requested' on a newly created subscription", e.getMessage());
+ }
+
+ subs.setId("ABC");
+ try {
+ ourClient.update().resource(subs).execute();
+ fail();
+ } catch (UnprocessableEntityException e) {
+ assertEquals("HTTP 422 Unprocessable Entity: Subscription.status must be 'requested' on a newly created subscription", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testUpdateFails() {
+ Subscription subs = new Subscription();
+ subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
+ subs.setStatus(SubscriptionStatusEnum.REQUESTED);
+ subs.setCriteria("Observation?identifier=123");
+ IIdType id = ourClient.create().resource(subs).execute().getId().toUnqualifiedVersionless();
+
+ subs.setId(id);
+
+ try {
+ subs.setStatus(SubscriptionStatusEnum.ACTIVE);
+ ourClient.update().resource(subs).execute();
+ fail();
+ } catch (UnprocessableEntityException e) {
+ assertEquals("HTTP 422 Unprocessable Entity: Subscription.status can not be changed from 'requested' to 'active'", e.getMessage());
+ }
+
+ try {
+ subs.setStatus((SubscriptionStatusEnum) null);
+ ourClient.update().resource(subs).execute();
+ fail();
+ } catch (UnprocessableEntityException e) {
+ assertEquals("HTTP 422 Unprocessable Entity: Subscription.status can not be changed from 'requested' to null", e.getMessage());
+ }
+
+ subs.setStatus(SubscriptionStatusEnum.OFF);
+ }
+
+ @Override
+ public void beforeCreateInterceptor() {
+ super.beforeCreateInterceptor();
+
+ SubscriptionsRequireManualActivationInterceptor interceptor = new SubscriptionsRequireManualActivationInterceptor();
+ interceptor.setDao(mySubscriptionDao);
+ myDaoConfig.getInterceptors().add(interceptor);
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-server-config.xml b/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-server-config.xml
index de236aebcff..1a6325c3666 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-server-config.xml
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/webapp/WEB-INF/hapi-fhir-server-config.xml
@@ -18,6 +18,7 @@
+
-
+
+
@@ -126,8 +120,7 @@
-
+
@@ -162,6 +155,10 @@
org.springframework
spring-beans
+
+ org.springframework.data
+ spring-data-jpa
+
org.springframework
spring-tx
@@ -226,7 +223,7 @@
com.google.guava
guava
-
+
org.eclipse.jetty
jetty-servlets
@@ -252,7 +249,7 @@
spring-test
test
-
+
@@ -277,10 +274,7 @@
org.apache.maven.plugins
maven-surefire-plugin
-
+
alphabetical
@@ -346,7 +340,7 @@
-
+
org.apache.maven.plugins
maven-jxr-plugin
@@ -373,17 +367,8 @@
SCRIPT
${skip-hib4}
-
+
o10g
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 a9479b8b625..8f675b4a2a3 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
@@ -999,6 +999,7 @@ public abstract class BaseHapiFhirDao implements IDao {
ResourceMetadataKeyEnum.VERSION.put(res, Long.toString(theEntity.getVersion()));
ResourceMetadataKeyEnum.PUBLISHED.put(res, theEntity.getPublished());
ResourceMetadataKeyEnum.UPDATED.put(res, theEntity.getUpdated());
+ IDao.RESOURCE_PID.put(res, theEntity.getId());
if (theEntity.getTitle() != null) {
ResourceMetadataKeyEnum.TITLE.put(res, theEntity.getTitle());
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
index 710d9826694..bc13f422a54 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
@@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.dao;
import static org.apache.commons.lang3.StringUtils.isBlank;
+import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -10,12 +11,15 @@ import javax.persistence.TypedQuery;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
+import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao;
import ca.uhn.fhir.jpa.entity.ResourceTable;
+import ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource;
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
@@ -31,6 +35,9 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2 flags = new ArrayList();
+ for (IBaseResource next : results.getResources(0, results.size())) {
+ SubscriptionFlaggedResource nextFlag = new SubscriptionFlaggedResource();
+ nextFlag.setResource();
+ }
+
}
@Override
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IDao.java
index cac3eca1687..052faf30a08 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IDao.java
@@ -1,5 +1,8 @@
package ca.uhn.fhir.jpa.dao;
+import ca.uhn.fhir.model.api.IResource;
+import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
+
/*
* #%L
* HAPI FHIR JPA Server
@@ -22,6 +25,21 @@ package ca.uhn.fhir.jpa.dao;
public interface IDao {
- void registerDaoListener(IDaoListener theListener);
+ public static final ResourceMetadataKeyEnum RESOURCE_PID = new ResourceMetadataKeyEnum("RESOURCE_PID") {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Long get(IResource theResource) {
+ return (Long) theResource.getResourceMetadata().get(RESOURCE_PID);
+ }
+
+ @Override
+ public void put(IResource theResource, Long theObject) {
+ theResource.getResourceMetadata().put(RESOURCE_PID, theObject);
+ }
+ };
+
+ void registerDaoListener(IDaoListener theListener);
+
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISubscriptionFlaggedResourceDataDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISubscriptionFlaggedResourceDataDao.java
new file mode 100644
index 00000000000..c6b1abc2ee5
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/ISubscriptionFlaggedResourceDataDao.java
@@ -0,0 +1,9 @@
+package ca.uhn.fhir.jpa.dao.data;
+
+import org.springframework.data.repository.CrudRepository;
+
+import ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource;
+
+public interface ISubscriptionFlaggedResourceDataDao extends CrudRepository {
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionFlaggedResource.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionFlaggedResource.java
index 73a7ae2f679..e9523dee549 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionFlaggedResource.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionFlaggedResource.java
@@ -1,16 +1,14 @@
package ca.uhn.fhir.jpa.entity;
-import java.util.Date;
-
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
-import javax.persistence.Temporal;
-import javax.persistence.TemporalType;
@Entity
@Table(name = "HFJ_SUBSCRIPTION_FLAG_RES")
@@ -22,8 +20,28 @@ public class SubscriptionFlaggedResource {
@Column(name = "PID", insertable = false, updatable = false)
private Long myId;
- @Temporal(TemporalType.TIMESTAMP)
- @Column(name = "CREATED", nullable = false)
- private Date myCreated;
+ @ManyToOne
+ @JoinColumn(name="RES_ID")
+ private ResourceTable myResource;
+
+ @ManyToOne()
+ @JoinColumn(name="SUBSCRIPTION_ID")
+ private SubscriptionTable mySubscription;
+ public ResourceTable getResource() {
+ return myResource;
+ }
+
+ public SubscriptionTable getSubscription() {
+ return mySubscription;
+ }
+
+ public void setResource(ResourceTable theResource) {
+ myResource = theResource;
+ }
+
+ public void setSubscription(SubscriptionTable theSubscription) {
+ mySubscription = theSubscription;
+ }
+
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionTable.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionTable.java
index 9ac60c9f6c6..baedc9e52f5 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionTable.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionTable.java
@@ -13,6 +13,7 @@ import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
@@ -65,6 +66,9 @@ public class SubscriptionTable {
@JoinColumn(name = "RES_ID", insertable = true, updatable = false, referencedColumnName = "RES_ID", foreignKey = @ForeignKey(name = "FK_SUBSCRIPTION_RESOURCE_ID") )
private ResourceTable mySubscriptionResource;
+ @OneToMany(orphanRemoval=true, mappedBy="mySubscription")
+ private SubscriptionFlaggedResource myFlaggedResources;
+
public long getCheckInterval() {
return myCheckInterval;
}
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 bb0fa08afcd..88bd044d3f2 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
@@ -1,6 +1,7 @@
@@ -21,6 +23,13 @@
#if ( ${versionCapitalized} == 'Dstu2' )
+
+
+
+
+
+
+
#end
diff --git a/pom.xml b/pom.xml
index 743cd97ebba..5645da54d42 100644
--- a/pom.xml
+++ b/pom.xml
@@ -503,6 +503,11 @@
spring-core
${spring_version}
+
+ org.springframework.data
+ spring-data-jpa
+ 1.9.0.RELEASE
+
org.springframework
spring-orm
From 7e6844be56ab72ce9df73c82bcd7ec0171cf93d5 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Thu, 24 Sep 2015 07:20:06 -0400
Subject: [PATCH 11/13] Improve error message when JSON parser finds an object
where an array is expected
---
.../java/ca/uhn/fhir/parser/JsonParser.java | 21 ++++--
.../uhn/fhir/parser/JsonParserDstu2Test.java | 38 +++++++----
.../src/test/resources/invalid_metadata.json | 65 +++++++++++++++++++
src/changes/changes.xml | 4 ++
4 files changed, 110 insertions(+), 18 deletions(-)
create mode 100644 hapi-fhir-structures-dstu2/src/test/resources/invalid_metadata.json
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
index 76c3d290da4..4c7874afebe 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
@@ -1014,7 +1014,7 @@ public class JsonParser extends BaseParser implements IParser {
if ("resourceType".equals(nextName)) {
continue;
} else if ("entry".equals(nextName)) {
- JsonArray entries = theObject.getJsonArray(nextName);
+ JsonArray entries = grabJsonArray(theObject, nextName, "entry");
for (JsonValue jsonValue : entries) {
theState.enteringNewElement(null, "entry");
parseBundleChildren((JsonObject) jsonValue, theState);
@@ -1023,7 +1023,7 @@ public class JsonParser extends BaseParser implements IParser {
continue;
} else if (myContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) {
if ("link".equals(nextName)) {
- JsonArray entries = theObject.getJsonArray(nextName);
+ JsonArray entries = grabJsonArray(theObject, nextName, "link");
for (JsonValue jsonValue : entries) {
theState.enteringNewElement(null, "link");
JsonObject linkObj = (JsonObject) jsonValue;
@@ -1042,7 +1042,7 @@ public class JsonParser extends BaseParser implements IParser {
}
} else {
if ("link".equals(nextName)) {
- JsonArray entries = theObject.getJsonArray(nextName);
+ JsonArray entries = grabJsonArray(theObject, nextName, "link");
for (JsonValue jsonValue : entries) {
theState.enteringNewElement(null, "link");
JsonObject linkObj = (JsonObject) jsonValue;
@@ -1106,11 +1106,11 @@ public class JsonParser extends BaseParser implements IParser {
}
continue;
} else if ("extension".equals(nextName)) {
- JsonArray array = theObject.getJsonArray(nextName);
+ JsonArray array = grabJsonArray(theObject, nextName, "extension");
parseExtension(theState, array, false);
continue;
} else if ("modifierExtension".equals(nextName)) {
- JsonArray array = theObject.getJsonArray(nextName);
+ JsonArray array = grabJsonArray(theObject, nextName, "modifierExtension");
parseExtension(theState, array, true);
continue;
} else if (nextName.charAt(0) == '_') {
@@ -1135,6 +1135,17 @@ public class JsonParser extends BaseParser implements IParser {
}
}
+ private JsonArray grabJsonArray(JsonObject theObject, String nextName, String thePosition) {
+ JsonValue object = theObject.get(nextName);
+ if (object == null) {
+ return null;
+ }
+ if (object.getValueType() != ValueType.ARRAY) {
+ throw new DataFormatException("Syntax error parsing JSON FHIR structure: Expected ARRAY at element '" + thePosition + "', found '" + object.getValueType().name() + "'");
+ }
+ return (JsonArray) object;
+ }
+
private void parseChildren(ParserState> theState, String theName, JsonValue theJsonVal, JsonValue theAlternateVal, String theAlternateName) {
switch (theJsonVal.getValueType()) {
case ARRAY: {
diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java
index 0e2f0c16c20..c99f26db979 100644
--- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java
+++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/JsonParserDstu2Test.java
@@ -28,6 +28,7 @@ import ca.uhn.fhir.model.dstu2.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu2.resource.Binary;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
+import ca.uhn.fhir.model.dstu2.resource.Conformance;
import ca.uhn.fhir.model.dstu2.resource.DiagnosticReport;
import ca.uhn.fhir.model.dstu2.resource.Medication;
import ca.uhn.fhir.model.dstu2.resource.MedicationOrder;
@@ -474,17 +475,6 @@ public class JsonParserDstu2Test {
assertThat(ourCtx.newJsonParser().setOmitResourceId(true).encodeResourceToString(p), not(containsString("123")));
}
- @Test
- public void testParseAndEncodeBundleResourceWithComments() throws Exception {
- String content = IOUtils.toString(JsonParserDstu2Test.class.getResourceAsStream("/bundle-transaction2.json"));
-
- ourCtx.newJsonParser().parseBundle(content);
-
- ca.uhn.fhir.model.dstu2.resource.Bundle parsed = ourCtx.newJsonParser().parseResource(ca.uhn.fhir.model.dstu2.resource.Bundle.class, content);
-
- // TODO: preserve comments
- }
-
@Test
public void testParseAndEncodeBundle() throws Exception {
String content = IOUtils.toString(JsonParserDstu2Test.class.getResourceAsStream("/bundle-example.json"));
@@ -527,7 +517,7 @@ public class JsonParserDstu2Test {
assertEquals(exp, act);
}
-
+
/**
* Test for #146
*/
@@ -641,6 +631,17 @@ public class JsonParserDstu2Test {
}
+ @Test
+ public void testParseAndEncodeBundleResourceWithComments() throws Exception {
+ String content = IOUtils.toString(JsonParserDstu2Test.class.getResourceAsStream("/bundle-transaction2.json"));
+
+ ourCtx.newJsonParser().parseBundle(content);
+
+ ca.uhn.fhir.model.dstu2.resource.Bundle parsed = ourCtx.newJsonParser().parseResource(ca.uhn.fhir.model.dstu2.resource.Bundle.class, content);
+
+ // TODO: preserve comments
+ }
+
@Test
public void testParseAndEncodeBundleWithDeletedEntry() {
@@ -856,7 +857,7 @@ public class JsonParserDstu2Test {
assertEquals(exp, act);
}
-
+
@Test
public void testParsePatientInBundle() {
@@ -917,6 +918,17 @@ public class JsonParserDstu2Test {
}
}
+ @Test
+ public void testParseWithWrongTypeObjectShouldBeArray() throws Exception {
+ String input = IOUtils.toString(getClass().getResourceAsStream("/invalid_metadata.json"));
+ try {
+ ourCtx.newJsonParser().parseResource(Conformance.class, input);
+ fail();
+ } catch (DataFormatException e) {
+ assertEquals("Syntax error parsing JSON FHIR structure: Expected ARRAY at element 'modifierExtension', found 'OBJECT'", e.getMessage());
+ }
+ }
+
/**
* See #144 and #146
*/
diff --git a/hapi-fhir-structures-dstu2/src/test/resources/invalid_metadata.json b/hapi-fhir-structures-dstu2/src/test/resources/invalid_metadata.json
new file mode 100644
index 00000000000..07b9c5a77e7
--- /dev/null
+++ b/hapi-fhir-structures-dstu2/src/test/resources/invalid_metadata.json
@@ -0,0 +1,65 @@
+{
+ "resourceType": "Conformance",
+ "meta": {
+ "versionId": "0.0.1"
+ },
+ "status": "draft",
+ "experimental": true,
+ "date": "2015-09-23T12:00:00Z",
+ "fhirVersion": "DSTU 2 0.5.0",
+ "acceptUnknown": false,
+ "format": [
+ "json"
+ ],
+ "rest": [
+ {
+ "mode": "server",
+ "documentation": "Information about the system's restful capabilities that apply across all applications, such as security",
+ "security": {
+ "cors": false,
+ "service": [
+ {
+ "coding": [
+ {
+ "system": "http://hl7.org/fhir/restful-security-service",
+ "code": "OAuth"
+ }
+ ]
+ }
+ ],
+ "description": "General description of how security works",
+ "certificate": [
+ {
+ "type": "json"
+ }
+ ],
+ "modifierExtension": {
+ "url": "http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris"
+ },
+ "extension": {
+ "url": "http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris"
+ }
+ },
+ "resource": [
+ {
+ "type": "Patient",
+ "profile": {
+ "reference": "http://hl7.org/fhir/StructureDefinition/patient-daf-dafpatient"
+ },
+ "interaction": [
+ {
+ "code": "read"
+ },
+ {
+ "code": "update"
+ },
+ {
+ "code": "search-type"
+ }
+ ],
+ "versioning": "no-version"
+ }
+ ]
+ }
+ ]
+}
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 8f8c2692189..ac61959a6b6 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -37,6 +37,10 @@
Constructor for DateRanfeParam which dates in two DateParam instances was ignoring
comparators on the DateParam.
+
+ In JSON parsing, finding an object where an array was expected led to an unhelpful
+ error message. Thanks to Avinash Shanbhag for reporting!
+
From 835abdfbea022c6c55ef521ac18da9df10c1e6c5 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Thu, 24 Sep 2015 07:46:35 -0400
Subject: [PATCH 12/13] Fix compile issues
---
.../ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java | 2 +-
.../main/java/ca/uhn/fhir/jpa/entity/SubscriptionTable.java | 3 ++-
hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans.vm | 3 +--
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
index bc13f422a54..c0033c1d83d 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java
@@ -98,7 +98,7 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2 flags = new ArrayList();
for (IBaseResource next : results.getResources(0, results.size())) {
SubscriptionFlaggedResource nextFlag = new SubscriptionFlaggedResource();
- nextFlag.setResource();
+// nextFlag.setResource();
}
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionTable.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionTable.java
index baedc9e52f5..46427ace7bc 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionTable.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionTable.java
@@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.entity;
+import java.util.Collection;
import java.util.Date;
import javax.persistence.Column;
@@ -67,7 +68,7 @@ public class SubscriptionTable {
private ResourceTable mySubscriptionResource;
@OneToMany(orphanRemoval=true, mappedBy="mySubscription")
- private SubscriptionFlaggedResource myFlaggedResources;
+ private Collection myFlaggedResources;
public long getCheckInterval() {
return myCheckInterval;
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 88bd044d3f2..bad99136679 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
@@ -23,8 +23,7 @@
#if ( ${versionCapitalized} == 'Dstu2' )
-
-
+
From 70eff0dc7f8a1e6b5d78d9badc4af9090c5a9db5 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Thu, 24 Sep 2015 08:36:25 -0400
Subject: [PATCH 13/13] Improve error message when $meta-add and $meta-delete
are called with no meta element in the input parameters. Also improve
OperationOutcome rendering in narrative generator.
---
.../uhn/fhir/narrative/OperationOutcome.html | 2 +-
.../provider/JpaResourceProviderDstu2.java | 7 +++
.../provider/ResourceProviderDstu2Test.java | 44 +++++++++++++++++++
...tThymeleafNarrativeGeneratorTestDstu2.java | 4 +-
src/changes/changes.xml | 8 ++++
5 files changed, 62 insertions(+), 3 deletions(-)
diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/OperationOutcome.html b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/OperationOutcome.html
index 4041ee58967..a84249a298f 100644
--- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/OperationOutcome.html
+++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/OperationOutcome.html
@@ -11,7 +11,7 @@
|
|
- |
+ |
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java
index cbaa640b52f..cb14c240d7b 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaResourceProviderDstu2.java
@@ -39,6 +39,7 @@ import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.server.EncodingEnum;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class JpaResourceProviderDstu2 extends BaseJpaResourceProvider {
@@ -112,6 +113,9 @@ public class JpaResourceProviderDstu2 extends BaseJpaResour
})
//@formatter:on
public Parameters metaAdd(@IdParam IdDt theId, @OperationParam(name = "meta") MetaDt theMeta) {
+ if (theMeta == null) {
+ throw new InvalidRequestException("Input contains no parameter with name 'meta'");
+ }
Parameters parameters = new Parameters();
MetaDt metaAddOperation = getDao().metaAddOperation(theId, theMeta);
parameters.addParameter().setName("return").setValue(metaAddOperation);
@@ -124,6 +128,9 @@ public class JpaResourceProviderDstu2 extends BaseJpaResour
})
//@formatter:on
public Parameters metaDelete(@IdParam IdDt theId, @OperationParam(name = "meta") MetaDt theMeta) {
+ if (theMeta == null) {
+ throw new InvalidRequestException("Input contains no parameter with name 'meta'");
+ }
Parameters parameters = new Parameters();
parameters.addParameter().setName("return").setValue(getDao().metaDeleteOperation(theId, theMeta));
return parameters;
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 7b76e5c81d1..bf6f6cf8bc0 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
@@ -747,6 +747,50 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
}
}
+ @Test
+ public void testMetaOperationWithNoMetaParameter() throws Exception {
+ Patient p = new Patient();
+ p.addName().addFamily("testMetaAddInvalid");
+ IIdType id = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless();
+
+ //@formatter:off
+ String input = "\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ "
\n" +
+ " \n" +
+ "";
+ //@formatter:on
+
+ HttpPost post = new HttpPost(ourServerBase + "/Patient/" + id.getIdPart() + "/$meta-add");
+ post.setEntity(new StringEntity(input, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
+ CloseableHttpResponse response = ourHttpClient.execute(post);
+ try {
+ String output = IOUtils.toString(response.getEntity().getContent());
+ ourLog.info(output);
+ assertEquals(400, response.getStatusLine().getStatusCode());
+ assertThat(output, containsString("Input contains no parameter with name 'meta'"));
+ } finally {
+ response.close();
+ }
+
+ post = new HttpPost(ourServerBase + "/Patient/" + id.getIdPart() + "/$meta-delete");
+ post.setEntity(new StringEntity(input, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
+ response = ourHttpClient.execute(post);
+ try {
+ String output = IOUtils.toString(response.getEntity().getContent());
+ ourLog.info(output);
+ assertEquals(400, response.getStatusLine().getStatusCode());
+ assertThat(output, containsString("Input contains no parameter with name 'meta'"));
+ } finally {
+ response.close();
+ }
+
+ }
+
@Test
public void testMetaOperations() throws Exception {
String methodName = "testMetaOperations";
diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTestDstu2.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTestDstu2.java
index 769e279a4f0..02e71f67872 100644
--- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTestDstu2.java
+++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTestDstu2.java
@@ -116,11 +116,11 @@ public class DefaultThymeleafNarrativeGeneratorTestDstu2 {
String parse = "\n" +
" \n" +
" \n" +
- " \n" +
+ " \n" +
" \n" +
" \n" +
" \n" +
- " \n" +
+ " \n" +
" \n" +
"";
//@formatter:on
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index ac61959a6b6..03e8ed1806d 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -41,6 +41,14 @@
In JSON parsing, finding an object where an array was expected led to an unhelpful
error message. Thanks to Avinash Shanbhag for reporting!
+
+ JPA server gave an unhelpful error message if $meta-add or $meta-delete were called
+ with no meta elements in the input Parameters
+
+
+ Narrative generator did not include OperationOutcome.issue.diagnostics in the
+ generated narrative.
+