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 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 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 extractValues(String thePaths, IResource theResource) { + private List extractValues(String thePath, IResource theResource) { List values = new ArrayList(); - String[] nextPathsSplit = thePaths.split("\\|"); FhirTerser t = getContext().newTerser(); - for (String nextPath : nextPathsSplit) { - String nextPathTrimmed = nextPath.trim(); - try { - values.addAll(t.getValues(theResource, nextPathTrimmed)); - } catch (Exception e) { - RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); - ourLog.warn("Failed to index values from path[{}] in resource type[{}]: ", new Object[] { nextPathTrimmed, def.getName(), e.toString() }); - } + String nextPathTrimmed = thePath.trim(); + try { + values.addAll(t.getValues(theResource, nextPathTrimmed)); + } catch (Exception e) { + RuntimeResourceDefinition def = myContext.getResourceDefinition(theResource); + ourLog.warn("Failed to index values from path[{}] in resource type[{}]: ", new Object[] { nextPathTrimmed, def.getName(), e.toString() }); } return values; } @@ -1109,25 +1109,25 @@ public abstract class BaseHapiFhirDao implements IDao { Collection paramsCoords = new ArrayList(theEntity.getParamsCoords()); Collection resourceLinks = new ArrayList(theEntity.getResourceLinks()); - List stringParams = null; - List tokenParams = null; - List numberParams = null; - List quantityParams = null; - List dateParams = null; - List uriParams = null; - List coordsParams = null; - List links = null; + Set stringParams = null; + Set tokenParams = null; + Set numberParams = null; + Set quantityParams = null; + Set dateParams = null; + Set uriParams = null; + Set coordsParams = null; + Set links = null; if (theDeletedTimestampOrNull != null) { - stringParams = Collections.emptyList(); - tokenParams = Collections.emptyList(); - numberParams = Collections.emptyList(); - quantityParams = Collections.emptyList(); - dateParams = Collections.emptyList(); - uriParams = Collections.emptyList(); - coordsParams = Collections.emptyList(); - links = Collections.emptyList(); + stringParams = Collections.emptySet(); + tokenParams = Collections.emptySet(); + numberParams = Collections.emptySet(); + quantityParams = Collections.emptySet(); + dateParams = Collections.emptySet(); + uriParams = Collections.emptySet(); + coordsParams = Collections.emptySet(); + links = Collections.emptySet(); theEntity.setDeleted(theDeletedTimestampOrNull); theEntity.setUpdated(theDeletedTimestampOrNull); @@ -1147,7 +1147,7 @@ public abstract class BaseHapiFhirDao implements IDao { // ourLog.info("Indexing resource: {}", entity.getId()); ourLog.trace("Storing string indexes: {}", stringParams); - tokenParams = new ArrayList(); + tokenParams = new HashSet(); for (BaseResourceIndexedSearchParam next : extractSearchParamTokens(theEntity, theResource)) { if (next instanceof ResourceIndexedSearchParamToken) { tokenParams.add((ResourceIndexedSearchParamToken) next); 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 916c4358a9d..bd9977b30e4 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 @@ -1,6 +1,5 @@ package ca.uhn.fhir.jpa.dao; -import static org.apache.commons.lang3.StringUtils.INDEX_NOT_FOUND; /* * #%L * HAPI FHIR JPA Server diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchParamExtractor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchParamExtractor.java index 58799408638..a072560042a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchParamExtractor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ISearchParamExtractor.java @@ -1,27 +1,6 @@ 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 java.util.ArrayList; -import java.util.List; +import java.util.Set; import ca.uhn.fhir.jpa.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamCoords; @@ -35,18 +14,18 @@ import ca.uhn.fhir.model.api.IResource; interface ISearchParamExtractor { - public abstract List extractSearchParamCoords(ResourceTable theEntity, IResource theResource); + public abstract Set extractSearchParamCoords(ResourceTable theEntity, IResource theResource); - public abstract List extractSearchParamDates(ResourceTable theEntity, IResource theResource); + public abstract Set extractSearchParamDates(ResourceTable theEntity, IResource theResource); - public abstract ArrayList extractSearchParamNumber(ResourceTable theEntity, IResource theResource); + public abstract Set extractSearchParamNumber(ResourceTable theEntity, IResource theResource); - public abstract List extractSearchParamQuantity(ResourceTable theEntity, IResource theResource); + public abstract Set extractSearchParamQuantity(ResourceTable theEntity, IResource theResource); - public abstract List extractSearchParamStrings(ResourceTable theEntity, IResource theResource); + public abstract Set extractSearchParamStrings(ResourceTable theEntity, IResource theResource); - public abstract List extractSearchParamTokens(ResourceTable theEntity, IResource theResource); + public abstract Set extractSearchParamTokens(ResourceTable theEntity, IResource theResource); - public abstract List extractSearchParamUri(ResourceTable theEntity, IResource theResource); + public abstract Set extractSearchParamUri(ResourceTable theEntity, IResource theResource); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu1.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu1.java index 6bced7ae6e3..ceff8c5756c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu1.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu1.java @@ -75,13 +75,13 @@ public class SearchParamExtractorDstu1 extends BaseSearchParamExtractor implemen } @Override - public List extractSearchParamCoords(ResourceTable theEntity, IResource theResource) { - return Collections.emptyList(); + public Set extractSearchParamCoords(ResourceTable theEntity, IResource theResource) { + return Collections.emptySet(); } @Override - public List extractSearchParamDates(ResourceTable theEntity, IResource theResource) { - ArrayList retVal = new ArrayList(); + public Set extractSearchParamDates(ResourceTable theEntity, IResource theResource) { + HashSet retVal = new HashSet(); RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { @@ -135,8 +135,8 @@ public class SearchParamExtractorDstu1 extends BaseSearchParamExtractor implemen } @Override - public ArrayList extractSearchParamNumber(ResourceTable theEntity, IResource theResource) { - ArrayList retVal = new ArrayList(); + public HashSet extractSearchParamNumber(ResourceTable theEntity, IResource theResource) { + HashSet retVal = new HashSet(); RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { @@ -230,8 +230,8 @@ public class SearchParamExtractorDstu1 extends BaseSearchParamExtractor implemen } @Override - public List extractSearchParamQuantity(ResourceTable theEntity, IResource theResource) { - ArrayList retVal = new ArrayList(); + public Set extractSearchParamQuantity(ResourceTable theEntity, IResource theResource) { + HashSet retVal = new HashSet(); RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { @@ -278,8 +278,8 @@ public class SearchParamExtractorDstu1 extends BaseSearchParamExtractor implemen } @Override - public List extractSearchParamStrings(ResourceTable theEntity, IResource theResource) { - ArrayList retVal = new ArrayList(); + public Set extractSearchParamStrings(ResourceTable theEntity, IResource theResource) { + HashSet retVal = new HashSet(); RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { @@ -364,8 +364,8 @@ public class SearchParamExtractorDstu1 extends BaseSearchParamExtractor implemen } @Override - public List extractSearchParamTokens(ResourceTable theEntity, IResource theResource) { - ArrayList retVal = new ArrayList(); + public Set extractSearchParamTokens(ResourceTable theEntity, IResource theResource) { + HashSet retVal = new HashSet(); RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { @@ -472,8 +472,8 @@ public class SearchParamExtractorDstu1 extends BaseSearchParamExtractor implemen } @Override - public List extractSearchParamUri(ResourceTable theEntity, IResource theResource) { - return Collections.emptyList(); + public Set extractSearchParamUri(ResourceTable theEntity, IResource theResource) { + return Collections.emptySet(); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu2.java index a23641d9ba5..b8bede8cb10 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParamExtractorDstu2.java @@ -84,7 +84,7 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen super(theContext); } - private void addSearchTerm(ResourceTable theEntity, ArrayList retVal, String resourceName, String searchTerm) { + private void addSearchTerm(ResourceTable theEntity, Set retVal, String resourceName, String searchTerm) { if (isBlank(searchTerm)) { return; } @@ -97,7 +97,7 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen retVal.add(nextEntity); } - private void addStringParam(ResourceTable theEntity, ArrayList retVal, RuntimeSearchParam nextSpDef, String value) { + private void addStringParam(ResourceTable theEntity, Set retVal, RuntimeSearchParam nextSpDef, String value) { if (value.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { value = value.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); } @@ -107,9 +107,9 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen } @Override - public List extractSearchParamCoords(ResourceTable theEntity, IResource theResource) { + public Set extractSearchParamCoords(ResourceTable theEntity, IResource theResource) { // TODO: implement - return Collections.emptyList(); + return Collections.emptySet(); } /* @@ -118,8 +118,8 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen * @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamDates(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IResource) */ @Override - public List extractSearchParamDates(ResourceTable theEntity, IResource theResource) { - ArrayList retVal = new ArrayList(); + public Set extractSearchParamDates(ResourceTable theEntity, IResource theResource) { + HashSet retVal = new HashSet(); RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { @@ -178,8 +178,8 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen * @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamNumber(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IResource) */ @Override - public ArrayList extractSearchParamNumber(ResourceTable theEntity, IResource theResource) { - ArrayList retVal = new ArrayList(); + public HashSet extractSearchParamNumber(ResourceTable theEntity, IResource theResource) { + HashSet retVal = new HashSet(); RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { @@ -272,8 +272,8 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen * @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamQuantity(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IResource) */ @Override - public List extractSearchParamQuantity(ResourceTable theEntity, IResource theResource) { - ArrayList retVal = new ArrayList(); + public Set extractSearchParamQuantity(ResourceTable theEntity, IResource theResource) { + HashSet retVal = new HashSet(); RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { @@ -326,8 +326,8 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen * @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamStrings(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IResource) */ @Override - public List extractSearchParamStrings(ResourceTable theEntity, IResource theResource) { - ArrayList retVal = new ArrayList(); + public Set extractSearchParamStrings(ResourceTable theEntity, IResource theResource) { + HashSet retVal = new HashSet(); RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { @@ -407,8 +407,8 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen * @see ca.uhn.fhir.jpa.dao.ISearchParamExtractor#extractSearchParamTokens(ca.uhn.fhir.jpa.entity.ResourceTable, ca.uhn.fhir.model.api.IResource) */ @Override - public List extractSearchParamTokens(ResourceTable theEntity, IResource theResource) { - ArrayList retVal = new ArrayList(); + public Set extractSearchParamTokens(ResourceTable theEntity, IResource theResource) { + HashSet retVal = new HashSet(); String useSystem = null; if (theResource instanceof ValueSet) { @@ -556,8 +556,8 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen } @Override - public List extractSearchParamUri(ResourceTable theEntity, IResource theResource) { - ArrayList retVal = new ArrayList(); + public Set extractSearchParamUri(ResourceTable theEntity, IResource theResource) { + HashSet retVal = new HashSet(); RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource); for (RuntimeSearchParam nextSpDef : def.getSearchParams()) { @@ -608,13 +608,13 @@ public class SearchParamExtractorDstu2 extends BaseSearchParamExtractor implemen private void extractTokensFromCodeableConcept(List theSystems, List theCodes, CodeableConceptDt theCodeableConcept, ResourceTable theEntity, - ArrayList theListToPopulate, RuntimeSearchParam theParameterDef) { + Set theListToPopulate, RuntimeSearchParam theParameterDef) { for (CodingDt nextCoding : theCodeableConcept.getCoding()) { extractTokensFromCoding(theSystems, theCodes, theEntity, theListToPopulate, theParameterDef, nextCoding); } } - private void extractTokensFromCoding(List theSystems, List theCodes, ResourceTable theEntity, ArrayList theListToPopulate, + private void extractTokensFromCoding(List theSystems, List theCodes, ResourceTable theEntity, Set theListToPopulate, RuntimeSearchParam theParameterDef, CodingDt nextCoding) { if (nextCoding != null && !nextCoding.isEmpty()) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamCoords.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamCoords.java index 7e6e4e88c8c..2f89be47f57 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamCoords.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamCoords.java @@ -24,6 +24,11 @@ import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + //@formatter:off @Entity @Table(name = "HFJ_SPIDX_COORDS" /* , indexes = { @Index(name = "IDX_SP_TOKEN", columnList = "SP_SYSTEM,SP_VALUE") } */) @@ -52,20 +57,59 @@ public class ResourceIndexedSearchParamCoords extends BaseResourceIndexedSearchP setLongitude(theLongitude); } - public double getLatitude() { - return myLatitude; + @Override + public boolean equals(Object theObj) { + if (this == theObj) { + return true; + } + if (theObj == null) { + return false; + } + if (!(theObj instanceof ResourceIndexedSearchParamCoords)) { + return false; + } + ResourceIndexedSearchParamCoords obj = (ResourceIndexedSearchParamCoords) theObj; + EqualsBuilder b = new EqualsBuilder(); + b.append(getParamName(), obj.getParamName()); + b.append(getResource(), obj.getResource()); + b.append(getLatitude(), obj.getLatitude()); + b.append(getLongitude(), obj.getLongitude()); + return b.isEquals(); } - public void setLatitude(double theLatitude) { - myLatitude = theLatitude; + public double getLatitude() { + return myLatitude; } public double getLongitude() { return myLongitude; } + @Override + public int hashCode() { + HashCodeBuilder b = new HashCodeBuilder(); + b.append(getParamName()); + b.append(getResource()); + b.append(getLatitude()); + b.append(getLongitude()); + return b.toHashCode(); + } + + public void setLatitude(double theLatitude) { + myLatitude = theLatitude; + } + public void setLongitude(double theLongitude) { myLongitude = theLongitude; } - + + @Override + public String toString() { + ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); + b.append("paramName", getParamName()); + b.append("resourceId", getResource().getId()); // TODO: add a field so we don't need to resolve this + b.append("lat", getLatitude()); + b.append("lon", getLongitude()); + return b.build(); + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamDate.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamDate.java index 303a2797089..140951b433a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamDate.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamDate.java @@ -28,6 +28,11 @@ import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + //@formatter:off @Entity @Table(name = "HFJ_SPIDX_DATE" /*, indexes= {@Index(name="IDX_SP_DATE", columnList= "SP_VALUE_LOW,SP_VALUE_HIGH")}*/) @@ -47,18 +52,34 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar @Temporal(TemporalType.TIMESTAMP) public Date myValueLow; - - public ResourceIndexedSearchParamDate() { } - + public ResourceIndexedSearchParamDate(String theName, Date theLow, Date theHigh) { setParamName(theName); setValueLow(theLow); setValueHigh(theHigh); } - + @Override + public boolean equals(Object theObj) { + if (this == theObj) { + return true; + } + if (theObj == null) { + return false; + } + if (!(theObj instanceof ResourceIndexedSearchParamDate)) { + return false; + } + ResourceIndexedSearchParamDate obj = (ResourceIndexedSearchParamDate) theObj; + EqualsBuilder b = new EqualsBuilder(); + b.append(getParamName(), obj.getParamName()); + b.append(getResource(), obj.getResource()); + b.append(getValueHigh(), obj.getValueHigh()); + b.append(getValueLow(), obj.getValueLow()); + return b.isEquals(); + } public Date getValueHigh() { return myValueHigh; @@ -68,6 +89,16 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar return myValueLow; } + @Override + public int hashCode() { + HashCodeBuilder b = new HashCodeBuilder(); + b.append(getParamName()); + b.append(getResource()); + b.append(getValueHigh()); + b.append(getValueLow()); + return b.toHashCode(); + } + public void setValueHigh(Date theValueHigh) { myValueHigh = theValueHigh; } @@ -76,6 +107,13 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar myValueLow = theValueLow; } - - + @Override + public String toString() { + ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); + b.append("paramName", getParamName()); + b.append("resourceId", getResource().getId()); // TODO: add a field so we don't need to resolve this + b.append("valueLow", getValueLow()); + b.append("valueHigh", getValueHigh()); + return b.build(); + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamNumber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamNumber.java index 405f778fae8..50d9dafabc2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamNumber.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamNumber.java @@ -26,6 +26,11 @@ import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + //@formatter:off @Entity @Table(name = "HFJ_SPIDX_NUMBER" /*, indexes= {@Index(name="IDX_SP_NUMBER", columnList="SP_VALUE")}*/ ) @@ -39,21 +44,57 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP @Column(name = "SP_VALUE", nullable = true) public BigDecimal myValue; - + public ResourceIndexedSearchParamNumber() { } - + public ResourceIndexedSearchParamNumber(String theParamName, BigDecimal theValue) { setParamName(theParamName); setValue(theValue); } + @Override + public boolean equals(Object theObj) { + if (this == theObj) { + return true; + } + if (theObj == null) { + return false; + } + if (!(theObj instanceof ResourceIndexedSearchParamNumber)) { + return false; + } + ResourceIndexedSearchParamNumber obj = (ResourceIndexedSearchParamNumber) theObj; + EqualsBuilder b = new EqualsBuilder(); + b.append(getParamName(), obj.getParamName()); + b.append(getResource(), obj.getResource()); + b.append(getValue(), obj.getValue()); + return b.isEquals(); + } + public BigDecimal getValue() { return myValue; } + @Override + public int hashCode() { + HashCodeBuilder b = new HashCodeBuilder(); + b.append(getParamName()); + b.append(getResource()); + b.append(getValue()); + return b.toHashCode(); + } + public void setValue(BigDecimal theValue) { myValue = theValue; } + @Override + public String toString() { + ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); + b.append("paramName", getParamName()); + b.append("resourceId", getResource().getId()); // TODO: add a field so we don't need to resolve this + b.append("value", getValue()); + return b.build(); + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamQuantity.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamQuantity.java index 71e4356dbb9..ab824f7d109 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamQuantity.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamQuantity.java @@ -26,6 +26,11 @@ import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + //@formatter:off @Entity @Table(name = "HFJ_SPIDX_QUANTITY" /*, indexes= {@Index(name="IDX_SP_NUMBER", columnList="SP_VALUE")}*/ ) @@ -47,9 +52,9 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc public BigDecimal myValue; public ResourceIndexedSearchParamQuantity() { - //nothing + // nothing } - + public ResourceIndexedSearchParamQuantity(String theParamName, BigDecimal theValue, String theSystem, String theUnits) { setParamName(theParamName); setSystem(theSystem); @@ -57,6 +62,27 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc setUnits(theUnits); } + @Override + public boolean equals(Object theObj) { + if (this == theObj) { + return true; + } + if (theObj == null) { + return false; + } + if (!(theObj instanceof ResourceIndexedSearchParamQuantity)) { + return false; + } + ResourceIndexedSearchParamQuantity obj = (ResourceIndexedSearchParamQuantity) theObj; + EqualsBuilder b = new EqualsBuilder(); + b.append(getParamName(), obj.getParamName()); + b.append(getResource(), obj.getResource()); + b.append(getSystem(), obj.getSystem()); + b.append(getUnits(), obj.getUnits()); + b.append(getValue(), obj.getValue()); + return b.isEquals(); + } + public String getSystem() { return mySystem; } @@ -69,6 +95,16 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc return myValue; } + @Override + public int hashCode() { + HashCodeBuilder b = new HashCodeBuilder(); + b.append(getParamName()); + b.append(getResource()); + b.append(getSystem()); + b.append(getUnits()); + b.append(getValue()); + return b.toHashCode(); + } public void setSystem(String theSystem) { mySystem = theSystem; @@ -82,4 +118,15 @@ public class ResourceIndexedSearchParamQuantity extends BaseResourceIndexedSearc myValue = theValue; } + @Override + public String toString() { + ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); + b.append("paramName", getParamName()); + b.append("resourceId", getResource().getId()); // TODO: add a field so we don't need to resolve this + b.append("system", getSystem()); + b.append("units", getUnits()); + b.append("value", getValue()); + return b.build(); + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamString.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamString.java index 493d1b08756..2e6b3860855 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamString.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamString.java @@ -25,25 +25,26 @@ import javax.persistence.Entity; import javax.persistence.Table; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @Entity -@Table(name = "HFJ_SPIDX_STRING"/*, indexes= {@Index(name="IDX_SP_STRING", columnList="SP_VALUE_NORMALIZED")}*/) -@org.hibernate.annotations.Table(appliesTo="HFJ_SPIDX_STRING",indexes= { - @org.hibernate.annotations.Index(name="IDX_SP_STRING", columnNames= {"RES_TYPE", "SP_NAME", "SP_VALUE_NORMALIZED"})}) +@Table(name = "HFJ_SPIDX_STRING"/* , indexes= {@Index(name="IDX_SP_STRING", columnList="SP_VALUE_NORMALIZED")} */) +@org.hibernate.annotations.Table(appliesTo = "HFJ_SPIDX_STRING", indexes = { @org.hibernate.annotations.Index(name = "IDX_SP_STRING", columnNames = { "RES_TYPE", "SP_NAME", "SP_VALUE_NORMALIZED" }) }) public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchParam { public static final int MAX_LENGTH = 100; private static final long serialVersionUID = 1L; + @Column(name = "SP_VALUE_EXACT", length = 100, nullable = true) + public String myValueExact; + @Column(name = "SP_VALUE_NORMALIZED", length = MAX_LENGTH, nullable = true) public String myValueNormalized; - @Column(name="SP_VALUE_EXACT",length=100,nullable=true) - public String myValueExact; - public ResourceIndexedSearchParamString() { } @@ -53,21 +54,42 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP setValueExact(theValueExact); } - public String getValueNormalized() { - return myValueNormalized; - } - - public void setValueNormalized(String theValueNormalized) { - if (StringUtils.defaultString(theValueNormalized).length() > MAX_LENGTH) { - throw new IllegalArgumentException("Value is too long: " + theValueNormalized.length()); + @Override + public boolean equals(Object theObj) { + if (this == theObj) { + return true; } - myValueNormalized = theValueNormalized; + if (theObj == null) { + return false; + } + if (!(theObj instanceof ResourceIndexedSearchParamString)) { + return false; + } + ResourceIndexedSearchParamString obj = (ResourceIndexedSearchParamString) theObj; + EqualsBuilder b = new EqualsBuilder(); + b.append(getParamName(), obj.getParamName()); + b.append(getResource(), obj.getResource()); + b.append(getValueExact(), obj.getValueExact()); + return b.isEquals(); } public String getValueExact() { return myValueExact; } + public String getValueNormalized() { + return myValueNormalized; + } + + @Override + public int hashCode() { + HashCodeBuilder b = new HashCodeBuilder(); + b.append(getParamName()); + b.append(getResource()); + b.append(getValueExact()); + return b.toHashCode(); + } + public void setValueExact(String theValueExact) { if (StringUtils.defaultString(theValueExact).length() > MAX_LENGTH) { throw new IllegalArgumentException("Value is too long: " + theValueExact.length()); @@ -75,6 +97,13 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP myValueExact = theValueExact; } + public void setValueNormalized(String theValueNormalized) { + if (StringUtils.defaultString(theValueNormalized).length() > MAX_LENGTH) { + throw new IllegalArgumentException("Value is too long: " + theValueNormalized.length()); + } + myValueNormalized = theValueNormalized; + } + @Override public String toString() { ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); @@ -83,5 +112,4 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP b.append("value", getValueNormalized()); return b.build(); } - } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java index 7f84b351e67..c22dd0ec877 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamToken.java @@ -25,13 +25,15 @@ import javax.persistence.Entity; import javax.persistence.Table; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; @Entity @Table(name = "HFJ_SPIDX_TOKEN" /* , indexes = { @Index(name = "IDX_SP_TOKEN", columnList = "SP_SYSTEM,SP_VALUE") } */) -@org.hibernate.annotations.Table(appliesTo = "HFJ_SPIDX_TOKEN", indexes = { - @org.hibernate.annotations.Index(name = "IDX_SP_TOKEN", columnNames = { "RES_TYPE", "SP_NAME", "SP_SYSTEM", "SP_VALUE" }), - @org.hibernate.annotations.Index(name = "IDX_SP_TOKEN_UNQUAL", columnNames = { "RES_TYPE", "SP_NAME", "SP_VALUE" }) -}) +@org.hibernate.annotations.Table(appliesTo = "HFJ_SPIDX_TOKEN", indexes = { @org.hibernate.annotations.Index(name = "IDX_SP_TOKEN", columnNames = { "RES_TYPE", "SP_NAME", "SP_SYSTEM", "SP_VALUE" }), + @org.hibernate.annotations.Index(name = "IDX_SP_TOKEN_UNQUAL", columnNames = { "RES_TYPE", "SP_NAME", "SP_VALUE" }) }) public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchParam { public static final int MAX_LENGTH = 100; @@ -53,6 +55,26 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa setValue(theValue); } + @Override + public boolean equals(Object theObj) { + if (this == theObj) { + return true; + } + if (theObj == null) { + return false; + } + if (!(theObj instanceof ResourceIndexedSearchParamToken)) { + return false; + } + ResourceIndexedSearchParamToken obj = (ResourceIndexedSearchParamToken) theObj; + EqualsBuilder b = new EqualsBuilder(); + b.append(getParamName(), obj.getParamName()); + b.append(getResource(), obj.getResource()); + b.append(getSystem(), obj.getSystem()); + b.append(getValue(), obj.getValue()); + return b.isEquals(); + } + public String getSystem() { return mySystem; } @@ -61,6 +83,16 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa return myValue; } + @Override + public int hashCode() { + HashCodeBuilder b = new HashCodeBuilder(); + b.append(getParamName()); + b.append(getResource()); + b.append(getSystem()); + b.append(getValue()); + return b.toHashCode(); + } + public void setSystem(String theSystem) { mySystem = StringUtils.defaultIfBlank(theSystem, null); } @@ -69,4 +101,13 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa myValue = StringUtils.defaultIfBlank(theValue, null); } + @Override + public String toString() { + ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); + b.append("paramName", getParamName()); + b.append("resourceId", getResource().getId()); // TODO: add a field so we don't need to resolve this + b.append("system", getSystem()); + b.append("value", getValue()); + return b.build(); + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamUri.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamUri.java index 3309724dfe3..2a2370fcc8c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamUri.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceIndexedSearchParamUri.java @@ -25,6 +25,8 @@ import javax.persistence.Entity; import javax.persistence.Table; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; //@formatter:off @@ -51,10 +53,38 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara setUri(theUri); } + @Override + public boolean equals(Object theObj) { + if (this == theObj) { + return true; + } + if (theObj == null) { + return false; + } + if (!(theObj instanceof ResourceIndexedSearchParamUri)) { + return false; + } + ResourceIndexedSearchParamUri obj = (ResourceIndexedSearchParamUri) theObj; + EqualsBuilder b = new EqualsBuilder(); + b.append(getParamName(), obj.getParamName()); + b.append(getResource(), obj.getResource()); + b.append(getUri(), obj.getUri()); + return b.isEquals(); + } + public String getUri() { return myUri; } + @Override + public int hashCode() { + HashCodeBuilder b = new HashCodeBuilder(); + b.append(getParamName()); + b.append(getResource()); + b.append(getUri()); + return b.toHashCode(); + } + public void setUri(String theUri) { myUri = StringUtils.defaultIfBlank(theUri, null); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceLink.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceLink.java index 519088cf4c0..27e3aa2e9dd 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceLink.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceLink.java @@ -32,11 +32,12 @@ import javax.persistence.ManyToOne; import javax.persistence.Table; import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; @Entity -@Table(name = "HFJ_RES_LINK"/*, indexes= {@Index(name="IDX_RL_TPATHRES", columnList= "SRC_PATH,TARGET_RESOURCE_ID")}*/) -@org.hibernate.annotations.Table(appliesTo="HFJ_RES_LINK",indexes= { - @org.hibernate.annotations.Index(name="IDX_RL_TPATHRES", columnNames= {"SRC_PATH", "TARGET_RESOURCE_ID"})}) +@Table(name = "HFJ_RES_LINK"/* , indexes= {@Index(name="IDX_RL_TPATHRES", columnList= "SRC_PATH,TARGET_RESOURCE_ID")} */) +@org.hibernate.annotations.Table(appliesTo = "HFJ_RES_LINK", indexes = { @org.hibernate.annotations.Index(name = "IDX_RL_TPATHRES", columnNames = { "SRC_PATH", "TARGET_RESOURCE_ID" }) }) public class ResourceLink implements Serializable { private static final long serialVersionUID = 1L; @@ -50,33 +51,21 @@ public class ResourceLink implements Serializable { private String mySourcePath; @ManyToOne(optional = false) - @JoinColumn(name = "SRC_RESOURCE_ID", referencedColumnName="RES_ID", nullable=false) + @JoinColumn(name = "SRC_RESOURCE_ID", referencedColumnName = "RES_ID", nullable = false) private ResourceTable mySourceResource; - @Column(name = "SRC_RESOURCE_ID", insertable = false, updatable = false, nullable=false) + @Column(name = "SRC_RESOURCE_ID", insertable = false, updatable = false, nullable = false) private Long mySourceResourcePid; @ManyToOne(optional = false) - @JoinColumn(name = "TARGET_RESOURCE_ID", referencedColumnName="RES_ID", nullable=false) + @JoinColumn(name = "TARGET_RESOURCE_ID", referencedColumnName = "RES_ID", nullable = false) private ResourceTable myTargetResource; - @Column(name = "TARGET_RESOURCE_ID", insertable = false, updatable = false,nullable=false) + @Column(name = "TARGET_RESOURCE_ID", insertable = false, updatable = false, nullable = false) private Long myTargetResourcePid; public ResourceLink() { - //nothing - } - - @Override - public String toString() { - StringBuilder b = new StringBuilder(); - b.append("ResourceLink["); - b.append("path=").append(mySourcePath); - b.append(", src=").append(mySourceResource.getId()); - b.append(", target=").append(myTargetResource.getId()); - - b.append("]"); - return b.toString(); + // nothing } public ResourceLink(String theSourcePath, ResourceTable theSourceResource, ResourceTable theTargetResource) { @@ -86,6 +75,25 @@ public class ResourceLink implements Serializable { myTargetResource = theTargetResource; } + @Override + public boolean equals(Object theObj) { + if (this == theObj) { + return true; + } + if (theObj == null) { + return false; + } + if (!(theObj instanceof ResourceLink)) { + return false; + } + ResourceLink obj = (ResourceLink) theObj; + EqualsBuilder b = new EqualsBuilder(); + b.append(mySourcePath, obj.mySourcePath); + b.append(mySourceResource, obj.mySourceResource); + b.append(myTargetResource, obj.myTargetResource); + return b.isEquals(); + } + public String getSourcePath() { return mySourcePath; } @@ -106,6 +114,15 @@ public class ResourceLink implements Serializable { return myTargetResourcePid; } + @Override + public int hashCode() { + HashCodeBuilder b = new HashCodeBuilder(); + b.append(mySourcePath); + b.append(mySourceResource); + b.append(myTargetResource); + return b.toHashCode(); + } + public void setSourcePath(String theSourcePath) { mySourcePath = theSourcePath; } @@ -114,17 +131,21 @@ public class ResourceLink implements Serializable { mySourceResource = theSourceResource; } - public void setSourceResourcePid(Long theSourceResourcePid) { - mySourceResourcePid = theSourceResourcePid; - } - public void setTargetResource(ResourceTable theTargetResource) { Validate.notNull(theTargetResource); myTargetResource = theTargetResource; } - public void setTargetResourcePid(Long theTargetResourcePid) { - myTargetResourcePid = theTargetResourcePid; + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("ResourceLink["); + b.append("path=").append(mySourcePath); + b.append(", src=").append(mySourceResource.getId()); + b.append(", target=").append(myTargetResource.getId()); + + b.append("]"); + return b.toString(); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java index 45e9bd316ac..2dc12fabdc4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTable.java @@ -19,14 +19,12 @@ package ca.uhn.fhir.jpa.entity; * limitations under the License. * #L% */ - -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.defaultString; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; -import java.util.List; import java.util.Set; import javax.persistence.CascadeType; @@ -329,7 +327,7 @@ public class ResourceTable extends BaseHasResource implements Serializable { myParamsDatePopulated = theParamsDatePopulated; } - public void setParamsNumber(List theNumberParams) { + public void setParamsNumber(Collection theNumberParams) { if (!isParamsNumberPopulated() && theNumberParams.isEmpty()) { return; } @@ -396,7 +394,7 @@ public class ResourceTable extends BaseHasResource implements Serializable { myProfile = theProfile; } - public void setResourceLinks(List theLinks) { + public void setResourceLinks(Collection theLinks) { if (!isHasLinks() && theLinks.isEmpty()) { return; } 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 3143be96d85..e72f4ce64ea 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 @@ -12,7 +12,6 @@ import javax.persistence.PersistenceContext; import org.apache.commons.io.IOUtils; import org.hl7.fhir.instance.model.api.IBaseResource; import org.junit.Before; -import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -42,9 +41,12 @@ import ca.uhn.fhir.jpa.entity.ResourceTag; import ca.uhn.fhir.jpa.entity.TagDefinition; import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; import ca.uhn.fhir.model.dstu2.resource.Bundle; +import ca.uhn.fhir.model.dstu2.resource.ConceptMap; import ca.uhn.fhir.model.dstu2.resource.Device; +import ca.uhn.fhir.model.dstu2.resource.DiagnosticOrder; import ca.uhn.fhir.model.dstu2.resource.DiagnosticReport; import ca.uhn.fhir.model.dstu2.resource.Encounter; +import ca.uhn.fhir.model.dstu2.resource.Immunization; import ca.uhn.fhir.model.dstu2.resource.Location; import ca.uhn.fhir.model.dstu2.resource.Observation; import ca.uhn.fhir.model.dstu2.resource.Organization; @@ -53,6 +55,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.Substance; import ca.uhn.fhir.model.dstu2.resource.ValueSet; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.method.MethodUtil; @@ -67,12 +70,21 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; //@formatter:on public abstract class BaseJpaDstu2Test extends BaseJpaTest { + @Autowired + @Qualifier("myConceptMapDaoDstu2") + protected IFhirResourceDao myConceptMapDao; + @Autowired + @Qualifier("mySubstanceDaoDstu2") + protected IFhirResourceDao mySubstanceDao; @Autowired protected DaoConfig myDaoConfig; @Autowired @Qualifier("myDeviceDaoDstu2") protected IFhirResourceDao myDeviceDao; @Autowired + @Qualifier("myDiagnosticOrderDaoDstu2") + protected IFhirResourceDao myDiagnosticOrderDao; + @Autowired @Qualifier("myDiagnosticReportDaoDstu2") protected IFhirResourceDao myDiagnosticReportDao; @Autowired @@ -82,6 +94,9 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest { protected EntityManager myEntityManager; @Autowired protected FhirContext myFhirCtx; + @Autowired + @Qualifier("myImmunizationDaoDstu2") + protected IFhirResourceDao myImmunizationDao; protected IServerInterceptor myInterceptor; @Autowired @Qualifier("myLocationDaoDstu2") 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 78ba3c9ca45..51b0a3eb090 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 @@ -24,7 +24,13 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.Test; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamDate; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamNumber; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamQuantity; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamUri; +import ca.uhn.fhir.jpa.entity.ResourceLink; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.Include; @@ -38,14 +44,18 @@ import ca.uhn.fhir.model.dstu2.composite.IdentifierDt; import ca.uhn.fhir.model.dstu2.composite.PeriodDt; import ca.uhn.fhir.model.dstu2.composite.QuantityDt; import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt; +import ca.uhn.fhir.model.dstu2.resource.ConceptMap; import ca.uhn.fhir.model.dstu2.resource.Device; +import ca.uhn.fhir.model.dstu2.resource.DiagnosticOrder; import ca.uhn.fhir.model.dstu2.resource.DiagnosticReport; import ca.uhn.fhir.model.dstu2.resource.Encounter; +import ca.uhn.fhir.model.dstu2.resource.Immunization; import ca.uhn.fhir.model.dstu2.resource.Location; import ca.uhn.fhir.model.dstu2.resource.Observation; import ca.uhn.fhir.model.dstu2.resource.Organization; import ca.uhn.fhir.model.dstu2.resource.Patient; 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.DateDt; @@ -70,6 +80,163 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu2SearchTest.class); + @Test + public void testIndexNoDuplicatesString() { + Patient p = new Patient(); + p.addAddress().addLine("123 Fake Street"); + p.addAddress().addLine("123 Fake Street"); + p.addAddress().addLine("123 Fake Street"); + p.addAddress().addLine("456 Fake Street"); + p.addAddress().addLine("456 Fake Street"); + p.addAddress().addLine("456 Fake Street"); + + IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless(); + + Class type = ResourceIndexedSearchParamString.class; + List results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList(); + ourLog.info(toStringMultiline(results)); + assertEquals(2, results.size()); + + List actual = toUnqualifiedVersionlessIds(myPatientDao.search(Patient.SP_ADDRESS, new StringParam("123 Fake Street"))); + assertThat(actual, contains(id)); + } + + @Test + public void testIndexNoDuplicatesDate() { + DiagnosticOrder order = new DiagnosticOrder(); + order.addItem().addEvent().setDateTime(new DateTimeDt("2011-12-11T11:12:12Z")); + order.addItem().addEvent().setDateTime(new DateTimeDt("2011-12-11T11:12:12Z")); + order.addItem().addEvent().setDateTime(new DateTimeDt("2011-12-11T11:12:12Z")); + order.addItem().addEvent().setDateTime(new DateTimeDt("2011-12-12T11:12:12Z")); + order.addItem().addEvent().setDateTime(new DateTimeDt("2011-12-12T11:12:12Z")); + order.addItem().addEvent().setDateTime(new DateTimeDt("2011-12-12T11:12:12Z")); + + IIdType id = myDiagnosticOrderDao.create(order).getId().toUnqualifiedVersionless(); + + List actual = toUnqualifiedVersionlessIds(myDiagnosticOrderDao.search(DiagnosticOrder.SP_ITEM_DATE, new DateParam("2011-12-12T11:12:12Z"))); + assertThat(actual, contains(id)); + + Class type = ResourceIndexedSearchParamDate.class; + List results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList(); + ourLog.info(toStringMultiline(results)); + assertEquals(2, results.size()); + } + + @Test + public void testIndexNoDuplicatesNumber() { + Immunization res = new Immunization(); + res.addVaccinationProtocol().setDoseSequence(1); + res.addVaccinationProtocol().setDoseSequence(1); + res.addVaccinationProtocol().setDoseSequence(1); + res.addVaccinationProtocol().setDoseSequence(2); + res.addVaccinationProtocol().setDoseSequence(2); + res.addVaccinationProtocol().setDoseSequence(2); + + IIdType id = myImmunizationDao.create(res).getId().toUnqualifiedVersionless(); + + List actual = toUnqualifiedVersionlessIds(myImmunizationDao.search(Immunization.SP_DOSE_SEQUENCE, new NumberParam("1"))); + assertThat(actual, contains(id)); + + Class type = ResourceIndexedSearchParamNumber.class; + List results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList(); + ourLog.info(toStringMultiline(results)); + assertEquals(2, results.size()); + } + + @Test + public void testIndexNoDuplicatesUri() { + ConceptMap res = new ConceptMap(); + res.addElement().addTarget().addDependsOn().setElement("http://foo"); + res.addElement().addTarget().addDependsOn().setElement("http://foo"); + res.addElement().addTarget().addDependsOn().setElement("http://bar"); + res.addElement().addTarget().addDependsOn().setElement("http://bar"); + + IIdType id = myConceptMapDao.create(res).getId().toUnqualifiedVersionless(); + + Class type = ResourceIndexedSearchParamUri.class; + List results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList(); + ourLog.info(toStringMultiline(results)); + assertEquals(2, results.size()); + + List actual = toUnqualifiedVersionlessIds(myConceptMapDao.search(ConceptMap.SP_DEPENDSON, new UriParam("http://foo"))); + assertThat(actual, contains(id)); + } + + @Test + public void testIndexNoDuplicatesQuantity() { + Substance res = new Substance(); + res.addInstance().getQuantity().setSystem("http://foo").setCode("UNIT").setValue(123); + res.addInstance().getQuantity().setSystem("http://foo").setCode("UNIT").setValue(123); + res.addInstance().getQuantity().setSystem("http://foo2").setCode("UNIT2").setValue(1232); + res.addInstance().getQuantity().setSystem("http://foo2").setCode("UNIT2").setValue(1232); + + IIdType id = mySubstanceDao.create(res).getId().toUnqualifiedVersionless(); + + Class type = ResourceIndexedSearchParamQuantity.class; + List results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList(); + ourLog.info(toStringMultiline(results)); + assertEquals(2, results.size()); + + List actual = toUnqualifiedVersionlessIds(mySubstanceDao.search(Substance.SP_QUANTITY, new QuantityParam(null, 123, "http://foo", "UNIT"))); + assertThat(actual, contains(id)); + } + + @Test + public void testIndexNoDuplicatesToken() { + Patient res = new Patient(); + res.addIdentifier().setSystem("http://foo1").setValue("123"); + res.addIdentifier().setSystem("http://foo1").setValue("123"); + res.addIdentifier().setSystem("http://foo2").setValue("1234"); + res.addIdentifier().setSystem("http://foo2").setValue("1234"); + + IIdType id = myPatientDao.create(res).getId().toUnqualifiedVersionless(); + + Class type = ResourceIndexedSearchParamToken.class; + List results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList(); + ourLog.info(toStringMultiline(results)); + assertEquals(2, results.size()); + + List actual = toUnqualifiedVersionlessIds(myPatientDao.search(Patient.SP_IDENTIFIER, new TokenParam("http://foo1", "123"))); + assertThat(actual, contains(id)); + } + + @Test + public void testIndexNoDuplicatesReference() { + Practitioner pract =new Practitioner(); + pract.setId("Practitioner/somepract"); + pract.getName().addFamily("SOME PRACT"); + myPractitionerDao.update(pract); + Practitioner pract2 =new Practitioner(); + pract2.setId("Practitioner/somepract2"); + pract2.getName().addFamily("SOME PRACT2"); + myPractitionerDao.update(pract2); + + DiagnosticOrder res = new DiagnosticOrder(); + res.addEvent().setActor(new ResourceReferenceDt("Practitioner/somepract")); + res.addEvent().setActor(new ResourceReferenceDt("Practitioner/somepract")); + res.addEvent().setActor(new ResourceReferenceDt("Practitioner/somepract2")); + res.addEvent().setActor(new ResourceReferenceDt("Practitioner/somepract2")); + + IIdType id = myDiagnosticOrderDao.create(res).getId().toUnqualifiedVersionless(); + + Class type = ResourceLink.class; + List results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList(); + ourLog.info(toStringMultiline(results)); + assertEquals(2, results.size()); + + List actual = toUnqualifiedVersionlessIds(myDiagnosticOrderDao.search(DiagnosticOrder.SP_ACTOR, new ReferenceParam("Practitioner/somepract"))); + assertThat(actual, contains(id)); + } + + private String toStringMultiline(List theResults) { + StringBuilder b = new StringBuilder(); + for (Object next : theResults) { + b.append('\n'); + b.append(" * ").append(next.toString()); + } + return b.toString(); + } + @Test public void testSearchAll() { { diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/SearchDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/SearchDstu2Test.java index 534060da738..e481fc4b9c2 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/SearchDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/SearchDstu2Test.java @@ -51,11 +51,13 @@ public class SearchDstu2Test { private static Server ourServer; private static String ourLastMethod; private static DateAndListParam ourLastDateAndList; + private static ReferenceParam ourLastRef; @Before public void before() { ourLastMethod = null; ourLastDateAndList = null; + ourLastRef = null; } @Test @@ -68,6 +70,59 @@ public class SearchDstu2Test { assertEquals(400, status.getStatusLine().getStatusCode()); } + + @Test + public void testSearchReferenceParams01() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=searchNoList&ref=123"); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + ourLog.info(responseContent); + assertEquals(200, status.getStatusLine().getStatusCode()); + + assertEquals("123", ourLastRef.getIdPart()); + assertEquals(null, ourLastRef.getResourceType()); + } + + @Test + public void testSearchReferenceParams02() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=searchNoList&ref=Patient/123"); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + ourLog.info(responseContent); + assertEquals(200, status.getStatusLine().getStatusCode()); + + assertEquals("123", ourLastRef.getIdPart()); + assertEquals("Patient", ourLastRef.getResourceType()); + } + + @Test + public void testSearchReferenceParams03() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=searchNoList&ref:Patient=Patient/123"); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + ourLog.info(responseContent); + assertEquals(200, status.getStatusLine().getStatusCode()); + + assertEquals("123", ourLastRef.getIdPart()); + assertEquals("Patient", ourLastRef.getResourceType()); + } + + @Test + public void testSearchReferenceParams04() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=searchNoList&ref:Patient=123"); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + ourLog.info(responseContent); + assertEquals(200, status.getStatusLine().getStatusCode()); + + assertEquals("123", ourLastRef.getIdPart()); + assertEquals("Patient", ourLastRef.getResourceType()); + } + @Test public void testSearchDateAndList() throws Exception { HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?searchDateAndList=2001,2002&searchDateAndList=2003,2004"); @@ -220,7 +275,17 @@ public class SearchDstu2Test { return Collections.emptyList(); } //@formatter:on - + + //@formatter:off + @Search(queryName="searchNoList") + public List searchNoList( + @RequiredParam(name = "ref") ReferenceParam theParam) { + ourLastMethod = "searchNoList"; + ourLastRef = theParam; + return Collections.emptyList(); + } + //@formatter:on + //@formatter:off @Search() public List searchDateAndList( diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 349eaa2d071..5de0f193023 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -6,6 +6,17 @@ HAPI FHIR Changelog + + + JPA server removes duplicate resource index entries before storing them + (e.g. if a patient has the same name twice, only one index entry is created + for that name) + + + 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 now validates QuestionnaireAnswers for conformance to their respective Questionnaire From 8fee057de326f395f67547e21fa62bbd717f0935 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Sun, 20 Sep 2015 10:18:33 -0400 Subject: [PATCH 03/13] Bump Spring and Hibernate versions --- hapi-fhir-jpaserver-base/pom.xml | 2 -- .../jpa/util/HapiDerbyTenSevenDialect.java | 16 ++++++++++++ .../src/main/resources/fhir-spring-config.xml | 2 +- .../test/resources/META-INF/persistence.xml | 5 ---- .../fhir-jpabase-spring-test-config.xml | 2 +- .../hapi-fhir-server-database-config.xml | 2 +- .../META-INF/fhirtest_persistence.xml | 2 +- ...hapi-fhir-server-database-config-dstu1.xml | 2 +- ...hapi-fhir-server-database-config-dstu2.xml | 2 +- .../resources/fhir-spring-test-config.xml | 2 +- .../resources/fhir_jpatest_persistence.xml | 2 +- .../test/resources/META-INF/persistence.xml | 2 +- .../fhir-jpabase-spring-test-config.xml | 2 +- pom.xml | 26 ++++++++++++++----- src/changes/changes.xml | 10 +++++++ 15 files changed, 55 insertions(+), 24 deletions(-) create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/HapiDerbyTenSevenDialect.java diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index 6b9aa9ac6a6..9237ebd9ce4 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -287,7 +287,6 @@ de.juplo hibernate4-maven-plugin - 1.0.5 true SCRIPT @@ -417,7 +416,6 @@ org.apache.maven.plugins maven-jxr-plugin - ${maven_jxr_plugin_version} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/HapiDerbyTenSevenDialect.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/HapiDerbyTenSevenDialect.java new file mode 100644 index 00000000000..c71c054a85f --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/HapiDerbyTenSevenDialect.java @@ -0,0 +1,16 @@ +package ca.uhn.fhir.jpa.util; + +import org.hibernate.dialect.DerbyTenSevenDialect; + +/** + * As of Hibernate 5.0.1, DerbyTenSevenDialect doesn't seem to work when updating + * the schema, as it tries to create a duplicate schema + */ +public class HapiDerbyTenSevenDialect extends DerbyTenSevenDialect { + + @Override + public String getQuerySequencesString() { + return "select SEQUENCENAME from sys.syssequences"; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/resources/fhir-spring-config.xml b/hapi-fhir-jpaserver-base/src/main/resources/fhir-spring-config.xml index 05bfbe60266..31c1630d367 100644 --- a/hapi-fhir-jpaserver-base/src/main/resources/fhir-spring-config.xml +++ b/hapi-fhir-jpaserver-base/src/main/resources/fhir-spring-config.xml @@ -35,7 +35,7 @@ - + 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 6feb16c727e..f990e6c30cf 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 @@ -25,12 +25,7 @@ false - - - - - diff --git a/hapi-fhir-jpaserver-base/src/test/resources/fhir-jpabase-spring-test-config.xml b/hapi-fhir-jpaserver-base/src/test/resources/fhir-jpabase-spring-test-config.xml index cc840460f79..3ff4d8ca889 100644 --- a/hapi-fhir-jpaserver-base/src/test/resources/fhir-jpabase-spring-test-config.xml +++ b/hapi-fhir-jpaserver-base/src/test/resources/fhir-jpabase-spring-test-config.xml @@ -33,7 +33,7 @@ - + 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 8e993d11811..c2dafc4915c 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 @@ -40,7 +40,7 @@ - + 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 8c4c112e780..a1d85bfa9c8 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 @@ -24,7 +24,7 @@ true - + diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu1.xml b/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu1.xml index 40f2e402ccb..13c6c5fdad2 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu1.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu1.xml @@ -44,7 +44,7 @@ - + diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu2.xml b/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu2.xml index 0d67234ab21..95fbacb6f31 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu2.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/resources/hapi-fhir-server-database-config-dstu2.xml @@ -31,7 +31,7 @@ - + diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/test/resources/fhir-spring-test-config.xml b/hapi-fhir-jpaserver-uhnfhirtest/src/test/resources/fhir-spring-test-config.xml index 6f24773a275..7e98295ef90 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/src/test/resources/fhir-spring-test-config.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/src/test/resources/fhir-spring-test-config.xml @@ -42,7 +42,7 @@ - + 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 cd25b565b8d..56306183fa6 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 @@ -21,7 +21,7 @@ - + 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 6feb16c727e..3dcefdcd452 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 @@ -27,7 +27,7 @@ - + diff --git a/hapi-fhir-testpage-overlay/src/test/resources/fhir-jpabase-spring-test-config.xml b/hapi-fhir-testpage-overlay/src/test/resources/fhir-jpabase-spring-test-config.xml index 2a8f7ae311b..65e49701718 100644 --- a/hapi-fhir-testpage-overlay/src/test/resources/fhir-jpabase-spring-test-config.xml +++ b/hapi-fhir-testpage-overlay/src/test/resources/fhir-jpabase-spring-test-config.xml @@ -38,7 +38,7 @@ - + diff --git a/pom.xml b/pom.xml index bcc3c08f1e7..574f0aa7cba 100644 --- a/pom.xml +++ b/pom.xml @@ -214,15 +214,13 @@ 10.11.1.1 - 4.2.17.Final - 5.1.0.Final + for that plugin... --> + 5.0.1.Final + 5.2.1.Final 9.2.6.v20141205 1.9.1 2.5.3 2.18.1 - 1.6 - 2.5 1.8 2.8 2.18.1 @@ -231,7 +229,7 @@ 1.1.8 2.7.1 4.3.6 - 4.1.5.RELEASE + 4.2.1.RELEASE 2.1.4.RELEASE 1.0.1 1.6 @@ -541,6 +539,11 @@ + + de.juplo + hibernate4-maven-plugin + 1.1.0 + org.apache.felix maven-bundle-plugin @@ -590,11 +593,21 @@ maven-deploy-plugin 2.8.2 + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + org.apache.maven.plugins maven-javadoc-plugin 2.10.1 + + org.apache.maven.plugins + maven-jxr-plugin + 2.5 + org.apache.maven.plugins maven-failsafe-plugin @@ -1223,7 +1236,6 @@ org.apache.maven.plugins maven-gpg-plugin - ${maven_gpg_plugin_version} sign-artifacts diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 5de0f193023..500eda8e512 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -8,6 +8,16 @@ + Bump the version of a few dependencies to the + latest versions (dependent HAPI modules listed in brackets): + +
  • Springframework (JPA, Web Tester): 4.1.5 -> 4.2.1
  • +
  • Hibernate (JPA, Web Tester): 4.2.17 -> 5.0.1
  • + + ]]> +
    + JPA server removes duplicate resource index entries before storing them (e.g. if a patient has the same name twice, only one index entry is created for that name) From 04c2cce13fd619e230bbe783b914dba261b9ca65 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Mon, 21 Sep 2015 09:08:19 -0400 Subject: [PATCH 04/13] Start working on JPA subscriptions --- .../main/java/ca/uhn/fhir/util/UrlUtil.java | 90 ++++++++ hapi-fhir-jpaserver-base/pom.xml | 139 ++++++------ .../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 103 ++++++--- .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 30 +-- .../fhir/jpa/dao/BaseHapiFhirSystemDao.java | 2 +- .../java/ca/uhn/fhir/jpa/dao/DaoConfig.java | 43 ++-- ...ResourceDaoQuestionnaireResponseDstu2.java | 5 +- .../dao/FhirResourceDaoSubscriptionDstu2.java | 195 +++++++++++++++++ .../jpa/dao/FhirResourceDaoValueSetDstu2.java | 2 +- .../uhn/fhir/jpa/dao/FhirSystemDaoDstu1.java | 9 +- .../uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java | 199 +++++------------- .../jpa/dao/IFhirResourceDaoSubscription.java | 36 ++++ .../uhn/fhir/jpa/entity/BaseHasResource.java | 6 +- .../entity/SubscriptionCandidateResource.java | 47 +++++ .../entity/SubscriptionFlaggedResource.java | 29 +++ .../fhir/jpa/entity/SubscriptionTable.java | 112 ++++++++++ .../ca/uhn/fhir/jpa/dao/BaseJpaDstu2Test.java | 25 ++- .../FhirResourceDaoDstu2SubscriptionTest.java | 156 ++++++++++++++ .../jpa/dao/FhirResourceDaoDstu2Test.java | 6 + .../test/resources/META-INF/persistence.xml | 3 + .../META-INF/fhirtest_persistence.xml | 3 + .../hapi-fhir-server-database-config.xml | 2 +- .../META-INF/fhirtest_persistence.xml | 3 + .../resources/fhir_jpatest_persistence.xml | 5 +- hapi-fhir-testpage-overlay/pom.xml | 11 - .../test/resources/META-INF/persistence.xml | 3 + .../src/main/resources/vm/jpa_spring_beans.vm | 2 +- pom.xml | 5 + src/changes/changes.xml | 4 + 29 files changed, 988 insertions(+), 287 deletions(-) create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoSubscription.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionCandidateResource.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionFlaggedResource.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionTable.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2SubscriptionTest.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java index dd11be7a8d1..a5b61eacef2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/UrlUtil.java @@ -6,6 +6,9 @@ import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; + /* * #%L * HAPI FHIR - Core Library @@ -158,5 +161,92 @@ public class UrlUtil { throw new Error("UTF-8 not supported on this platform"); } } + + //@formatter:off + /** + * Parse a URL in one of the following forms: + *
      + *
    • [Resource Type]?[Search Params] + *
    • [Resource Type]/[Resource ID] + *
    • [Resource Type]/[Resource ID]/_history/[Version ID] + *
    + */ + //@formatter:on + public static UrlParts parseUrl(String theUrl) { + UrlParts retVal = new UrlParts(); + + 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 { + throw new InvalidRequestException("Invalid FHIR resource URL: " + theUrl); + } + } + if (nextChar == '?') { + if (theUrl.length() > idx + 1) { + retVal.setParams(theUrl.substring(idx + 1, theUrl.length())); + } + break; + } + nextStart = idx + 1; + } + } + + return retVal; + } + + public static class UrlParts { + private String myParams; + private String myResourceId; + private String myResourceType; + private String myVersionId; + + public String getParams() { + return myParams; + } + + public String getResourceId() { + return myResourceId; + } + + public String getResourceType() { + return myResourceType; + } + + public String getVersionId() { + return myVersionId; + } + + 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/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index 9237ebd9ce4..df590b1415e 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -284,72 +284,6 @@ alphabetical
    - - 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 - - - - ca.uhn.hapi.fhir hapi-tinder-plugin @@ -427,6 +361,79 @@ true + + 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 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 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 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 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 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 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 theClass) { IFhirResourceDao 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 myDao; - private String myParams; - private String myResourceId; - private String myResourceType; - private String myVersionId; - - public IFhirResourceDao 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 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 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 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 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 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. +