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 - + + @@ -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,84 +274,14 @@ org.apache.maven.plugins maven-surefire-plugin - + alphabetical - - de.juplo - hibernate4-maven-plugin - 1.0.5 - - 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 - 1.2 + 1.3-SNAPSHOT build_dstu1 @@ -389,12 +316,12 @@ 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 @@ -413,11 +340,10 @@ - + org.apache.maven.plugins maven-jxr-plugin - ${maven_jxr_plugin_version} @@ -429,6 +355,70 @@ 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 952362ae003..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 @@ -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; } @@ -520,7 +520,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 +558,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 +930,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,14 +977,29 @@ 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())); 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()); @@ -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); @@ -1109,25 +1121,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 +1159,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); @@ -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,12 @@ public abstract class BaseHapiFhirDao implements IDao { myEntityManager.persist(theEntity.getForcedId()); } + postPersist(theEntity, (T) theResource); + } else { theEntity = myEntityManager.merge(theEntity); + + postUpdate(theEntity, (T) theResource); } if (thePerformIndexing) { @@ -1289,6 +1305,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 +1336,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 916c4358a9d..2b1bba91465 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 @@ -143,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; @@ -246,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 // } private Set addPredicateLanguage(Set thePids, List> theList) { + Set retVal = thePids; if (theList == null || theList.isEmpty()) { - return thePids; - } - if (theList.size() > 1) { - throw new InvalidRequestException("Language parameter can not have more than one AND value, found " + theList.size()); + return retVal; } + for (List nextList : theList) { - CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); - CriteriaQuery cq = builder.createQuery(Long.class); - Root from = cq.from(ResourceTable.class); - cq.select(from.get("myId").as(Long.class)); + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery cq = builder.createQuery(Long.class); + Root from = cq.from(ResourceTable.class); + cq.select(from.get("myId").as(Long.class)); - Set values = new HashSet(); - for (IQueryParameterType next : theList.get(0)) { - if (next instanceof StringParam) { - String nextValue = ((StringParam) next).getValue(); - if (isBlank(nextValue)) { - continue; + Set values = new HashSet(); + for (IQueryParameterType next : nextList) { + if (next instanceof StringParam) { + String nextValue = ((StringParam) next).getValue(); + if (isBlank(nextValue)) { + continue; + } + values.add(nextValue); + } else { + throw new InternalErrorException("Lanugage parameter must be of type " + StringParam.class.getCanonicalName() + " - Got " + next.getClass().getCanonicalName()); } - values.add(nextValue); + } + + if (values.isEmpty()) { + return retVal; + } + + Predicate typePredicate = builder.equal(from.get("myResourceType"), myResourceName); + Predicate langPredicate = from.get("myLanguage").as(String.class).in(values); + Predicate masterCodePredicate = builder.and(typePredicate, langPredicate); + Predicate notDeletedPredicate = builder.isNull(from.get("myDeleted")); + + if (retVal.size() > 0) { + Predicate inPids = (from.get("myId").in(retVal)); + cq.where(builder.and(masterCodePredicate, inPids, notDeletedPredicate)); } else { - throw new InternalErrorException("Lanugage parameter must be of type " + StringParam.class.getCanonicalName() + " - Got " + next.getClass().getCanonicalName()); + cq.where(builder.and(masterCodePredicate, notDeletedPredicate)); + } + + TypedQuery q = myEntityManager.createQuery(cq); + retVal = new HashSet(q.getResultList()); + if (retVal.isEmpty()) { + return retVal; } } - if (values.isEmpty()) { - return thePids; - } - - Predicate typePredicate = builder.equal(from.get("myResourceType"), myResourceName); - Predicate langPredicate = from.get("myLanguage").as(String.class).in(values); - Predicate masterCodePredicate = builder.and(typePredicate, langPredicate); - Predicate notDeletedPredicate = builder.isNull(from.get("myDeleted")); - - if (thePids.size() > 0) { - Predicate inPids = (from.get("myId").in(thePids)); - cq.where(builder.and(masterCodePredicate, inPids, notDeletedPredicate)); - } else { - cq.where(builder.and(masterCodePredicate, notDeletedPredicate)); - } - - TypedQuery q = myEntityManager.createQuery(cq); - return new HashSet(q.getResultList()); + return retVal; } private boolean addPredicateMissingFalseIfPresent(CriteriaBuilder theBuilder, String theParamName, Root from, List codePredicates, IQueryParameterType nextOr) { @@ -1005,7 +1010,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) { @@ -1269,7 +1274,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(); @@ -1299,14 +1305,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); @@ -1347,7 +1354,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); @@ -1420,7 +1427,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; } @@ -1497,7 +1504,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; @@ -1528,7 +1535,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; } @@ -1547,7 +1554,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()); @@ -1828,7 +1835,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()) { @@ -2066,7 +2073,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; } @@ -2126,42 +2133,41 @@ public abstract class BaseHapiFhirResourceDao extends BaseH if (nextParamEntry.getValue().isEmpty()) { continue; - } else if (nextParamEntry.getValue().size() > 1) { - throw new InvalidRequestException("AND queries not supported for _id (Multiple instances of this param found)"); } else { - Set joinPids = new HashSet(); - List 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); + } } } @@ -2382,7 +2388,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()); } } @@ -2399,7 +2405,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; + private long mySubscriptionPollDelay = 1000; /** * See {@link #setIncludeLimit(int)} @@ -64,6 +66,17 @@ public class DaoConfig { return myResourceEncoding; } + public long getSubscriptionPollDelay() { + return mySubscriptionPollDelay; + } + + /** + * See {@link #setSubscriptionEnabled(boolean)} + */ + public boolean isSubscriptionEnabled() { + return mySubscriptionEnabled; + } + public void setHardSearchLimit(int theHardSearchLimit) { myHardSearchLimit = theHardSearchLimit; } @@ -90,12 +103,11 @@ public class DaoConfig { * ID). *

*/ - public void setInterceptors(List theInterceptors) { - myInterceptors = theInterceptors; - } - - public void setResourceEncoding(ResourceEncodingEnum theResourceEncoding) { - myResourceEncoding = theResourceEncoding; + public void setInterceptors(IServerInterceptor... theInterceptor) { + setInterceptors(new ArrayList()); + if (theInterceptor != null && theInterceptor.length != 0) { + getInterceptors().addAll(Arrays.asList(theInterceptor)); + } } /** @@ -107,12 +119,27 @@ 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; + } + + public void setSubscriptionPollDelay(long theSubscriptionPollDelay) { + mySubscriptionPollDelay = theSubscriptionPollDelay; } } 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..c0033c1d83d --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSubscriptionDstu2.java @@ -0,0 +1,183 @@ +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; + +import javax.persistence.Query; +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; +import ca.uhn.fhir.model.dstu2.resource.Subscription; +import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.rest.param.DateParam; +import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.server.IBundleProvider; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; + +public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2implements IFhirResourceDaoSubscription { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSubscriptionDstu2.class); + + @Autowired + private ISubscriptionFlaggedResourceDataDao mySubscriptionFlaggedResourceDataDao; + + private void createSubscriptionTable(ResourceTable theEntity, Subscription theSubscription) { + SubscriptionTable subscriptionEntity = new SubscriptionTable(); + subscriptionEntity.setSubscriptionResource(theEntity); + subscriptionEntity.setNextCheck(theEntity.getPublished().getValue()); + subscriptionEntity.setMostRecentMatch(theEntity.getPublished().getValue()); + subscriptionEntity.setStatus(theSubscription.getStatusElement().getValueAsEnum()); + myEntityManager.persist(subscriptionEntity); + } + + @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()"); + +// SubscriptionCandidateResource + + 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(); + + for (SubscriptionTable nextSubscriptionTable : subscriptions) { + pollForNewUndeliveredResources(nextSubscriptionTable); + } + } + + private void pollForNewUndeliveredResources(SubscriptionTable theSubscriptionTable) { + Subscription subscription = toResource(Subscription.class, theSubscriptionTable.getSubscriptionResource(), false); + RuntimeResourceDefinition resourceDef = validateCriteriaAndReturnResourceDefinition(subscription); + SearchParameterMap criteriaUrl = translateMatchUrl(subscription.getCriteria(), resourceDef); + + criteriaUrl = new SearchParameterMap();//TODO:remove + long start = theSubscriptionTable.getMostRecentMatch().getTime(); + long end = System.currentTimeMillis() - getConfig().getSubscriptionPollDelay(); + if (end <= start) { + ourLog.trace("Skipping search for subscription"); + return; + } + ourLog.info("Subscription search from {} to {}", start, end); + + DateRangeParam range = new DateRangeParam(); + range.setLowerBound(new DateParam(QuantityCompararatorEnum.GREATERTHAN, start)); + range.setUpperBound(new DateParam(QuantityCompararatorEnum.LESSTHAN, end)); + criteriaUrl.setLastUpdated(range); + + IFhirResourceDao dao = getDao(resourceDef.getImplementingClass()); + IBundleProvider results = dao.search(criteriaUrl); + if (results.size() == 0) { + return; + } + + ourLog.info("Found {} new results for Subscription {}", results.size(), subscription.getId().getIdPart()); + + List flags = new ArrayList(); + for (IBaseResource next : results.getResources(0, results.size())) { + SubscriptionFlaggedResource nextFlag = new SubscriptionFlaggedResource(); +// nextFlag.setResource(); + } + + } + + @Override + protected void postPersist(ResourceTable theEntity, Subscription theSubscription) { + super.postPersist(theEntity, theSubscription); + + createSubscriptionTable(theEntity, theSubscription); + } + + @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); + + RuntimeResourceDefinition resDef = validateCriteriaAndReturnResourceDefinition(theResource); + + IFhirResourceDao dao = getDao(resDef.getImplementingClass()); + if (dao == null) { + throw new UnprocessableEntityException("Subscription.criteria contains invalid/unsupported resource type: " + resDef); + } + + if (theResource.getChannel().getType() == null) { + throw new UnprocessableEntityException("Subscription.channel.type must be populated on this server"); + } + + SubscriptionStatusEnum status = theResource.getStatusElement().getValueAsEnum(); + if (status == null) { + throw new UnprocessableEntityException("Subscription.status must be populated on this server"); + } + + } + + private RuntimeResourceDefinition validateCriteriaAndReturnResourceDefinition(Subscription theResource) { + 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); + } + return resDef; + } + +} 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/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/IFhirResourceDaoSubscription.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoSubscription.java new file mode 100644 index 00000000000..2cc89d82ceb --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDaoSubscription.java @@ -0,0 +1,29 @@ +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; + +public interface IFhirResourceDaoSubscription extends IFhirResourceDao { + + void pollForNewUndeliveredResources(); + +} 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/dao/SearchParameterMap.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java index 9e2a37f3bfb..fee9d22c9ca 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchParameterMap.java @@ -21,8 +21,8 @@ package ca.uhn.fhir.jpa.dao; */ import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Set; @@ -36,7 +36,7 @@ import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.param.DateRangeParam; -public class SearchParameterMap extends HashMap>> { +public class SearchParameterMap extends LinkedHashMap>> { private static final long serialVersionUID = 1L; 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/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/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/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..e9523dee549 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionFlaggedResource.java @@ -0,0 +1,47 @@ +package ca.uhn.fhir.jpa.entity; + +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; + +@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; + + @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 new file mode 100644 index 00000000000..46427ace7bc --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/SubscriptionTable.java @@ -0,0 +1,117 @@ +package ca.uhn.fhir.jpa.entity; + +import java.util.Collection; +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.OneToMany; +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 = "MOST_RECENT_MATCH", nullable = false) + private Date myMostRecentMatch; + + @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; + + @OneToMany(orphanRemoval=true, mappedBy="mySubscription") + private Collection myFlaggedResources; + + public long getCheckInterval() { + return myCheckInterval; + } + + public Long getId() { + return myId; + } + + public Date getNextCheck() { + return myNextCheck; + } + + 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 setStatus(SubscriptionStatusEnum theStatus) { + myStatus = theStatus; + } + + public void setSubscriptionResource(ResourceTable theSubscriptionResource) { + mySubscriptionResource = theSubscriptionResource; + } + + public Date getMostRecentMatch() { + return myMostRecentMatch; + } + + public void setMostRecentMatch(Date theMostRecentMatch) { + myMostRecentMatch = theMostRecentMatch; + } + +} 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..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 @@ -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,13 @@ 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; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 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 @@ -123,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); @@ -135,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; @@ -150,10 +146,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/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/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/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/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..dbdc63ae895 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,13 +12,11 @@ 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; 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; @@ -39,12 +37,17 @@ 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.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; +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 +56,8 @@ 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; import ca.uhn.fhir.rest.method.MethodUtil; @@ -63,16 +68,21 @@ 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 { + @Autowired + @Qualifier("myConceptMapDaoDstu2") + protected IFhirResourceDao myConceptMapDao; @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 +92,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") @@ -111,6 +124,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 @@ -145,6 +164,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); @@ -159,6 +185,7 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest { txTemplate.execute(new TransactionCallback() { @Override public Void doInTransaction(TransactionStatus theStatus) { + 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(); @@ -174,6 +201,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/FhirResourceDaoDstu2SearchTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2SearchTest.java index 78ba3c9ca45..9137609fc73 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; @@ -24,7 +25,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,19 +45,25 @@ 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.CodeDt; import ca.uhn.fhir.model.primitive.DateDt; import ca.uhn.fhir.model.primitive.DateTimeDt; import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.DateParam; @@ -58,6 +71,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; @@ -70,6 +85,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() { { @@ -130,6 +302,151 @@ 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; + { + 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(); @@ -201,7 +518,6 @@ public class FhirResourceDaoDstu2SearchTest extends BaseJpaDstu2Test { assertEquals(0, retrieved.size()); } } - @Test public void testSearchLanguageParam() { IIdType id1; @@ -240,9 +556,155 @@ 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()); + } + { + 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("")).addOr(new StringParam(null))); + params.add(Patient.SP_RES_LANGUAGE, and); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1)); + } + { + SearchParameterMap params = new SearchParameterMap(); + params.add("_id", new StringParam(id1.getIdPart())); + StringAndListParam and = new StringAndListParam(); + and.addAnd(new StringOrListParam().addOr(new StringParam("en_CA")).addOr(new StringParam("ZZZZ"))); + and.addAnd(new StringOrListParam().addOr(new StringParam("")).addOr(new StringParam(null))); + 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("")).addOr(new StringParam(null))); + params.add(Patient.SP_RES_LANGUAGE, and); + params.add("_id", new StringParam(id1.getIdPart())); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(params)), containsInAnyOrder(id1)); + } } + @Test + public void testSearchLastUpdatedParamWithComparator() throws InterruptedException { + String methodName = "testSearchLastUpdatedParamWithComparator"; + + IIdType id0; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + id0 = myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + } + + int sleep = 100; + + long start = System.currentTimeMillis(); + Thread.sleep(sleep); + + DateTimeDt beforeAny = new DateTimeDt(new Date(), TemporalPrecisionEnum.MILLI); + IIdType id1a; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + id1a = myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + } + IIdType id1b; + { + Patient patient = new Patient(); + patient.addIdentifier().setSystem("urn:system").setValue("001"); + id1b = myPatientDao.create(patient).getId().toUnqualifiedVersionless(); + } + + ourLog.info("Res 1: {}", ResourceMetadataKeyEnum.PUBLISHED.get(myPatientDao.read(id0)).getValueAsString()); + ourLog.info("Res 2: {}", ResourceMetadataKeyEnum.PUBLISHED.get(myPatientDao.read(id1a)).getValueAsString()); + InstantDt id1bpublished = ResourceMetadataKeyEnum.PUBLISHED.get(myPatientDao.read(id1b)); + ourLog.info("Res 3: {}", id1bpublished.getValueAsString()); + + + Thread.sleep(sleep); + long end = System.currentTimeMillis(); + + SearchParameterMap map; + Date startDate = new Date(start); + Date endDate = new Date(end); + DateTimeDt startDateTime = new DateTimeDt(startDate, TemporalPrecisionEnum.MILLI); + DateTimeDt endDateTime = new DateTimeDt(endDate, TemporalPrecisionEnum.MILLI); + + map = new SearchParameterMap(); + map.setLastUpdated(new DateRangeParam(startDateTime, endDateTime)); + ourLog.info("Searching: {}", map.getLastUpdated()); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), containsInAnyOrder(id1a, id1b)); + + map = new SearchParameterMap(); + map.setLastUpdated(new DateRangeParam(new DateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, startDateTime), new DateParam(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS, endDateTime))); + ourLog.info("Searching: {}", map.getLastUpdated()); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), containsInAnyOrder(id1a, id1b)); + + map = new SearchParameterMap(); + map.setLastUpdated(new DateRangeParam(new DateParam(QuantityCompararatorEnum.GREATERTHAN, startDateTime), new DateParam(QuantityCompararatorEnum.LESSTHAN, endDateTime))); + ourLog.info("Searching: {}", map.getLastUpdated()); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), containsInAnyOrder(id1a, id1b)); + + map = new SearchParameterMap(); + map.setLastUpdated(new DateRangeParam(new DateParam(QuantityCompararatorEnum.GREATERTHAN, startDateTime.getValue()), new DateParam(QuantityCompararatorEnum.LESSTHAN, id1bpublished.getValue()))); + ourLog.info("Searching: {}", map.getLastUpdated()); + assertThat(toUnqualifiedVersionlessIds(myPatientDao.search(map)), containsInAnyOrder(id1a)); + } + @Test public void testSearchLastUpdatedParam() throws InterruptedException { String methodName = "testSearchLastUpdatedParam"; 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..68018559a09 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2SubscriptionTest.java @@ -0,0 +1,174 @@ +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.setStatus(SubscriptionStatusEnum.REQUESTED); + 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.setStatus(SubscriptionStatusEnum.REQUESTED); + 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.setStatus(SubscriptionStatusEnum.REQUESTED); + 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.setStatus(SubscriptionStatusEnum.REQUESTED); + 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.setStatus(SubscriptionStatusEnum.REQUESTED); + 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() throws Exception { + myDaoConfig.setSubscriptionPollDelay(0); + + 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/" + pId.getIdPart()); + subs.setStatus(SubscriptionStatusEnum.ACTIVE); + IIdType id = mySubscriptionDao.create(subs).getId().toUnqualifiedVersionless(); + + Thread.sleep(100); + ourLog.info("Before: {}", System.currentTimeMillis()); + + 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(); + + Thread.sleep(100); + + ourLog.info("After: {}", System.currentTimeMillis()); + + mySubscriptionDao.pollForNewUndeliveredResources(); + } + + @Test + public void testCreateSubscription() { + 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(); + + 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()); + + 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()); + + 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); + subs.setStatus(SubscriptionStatusEnum.REQUESTED); + 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/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..7206040068e --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/BaseResourceProviderDstu2Test.java @@ -0,0 +1,126 @@ +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(); + ourServer = null; + ourHttpClient = null; + } + + @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 bc760d1c938..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 @@ -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; @@ -151,6 +128,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; @@ -202,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(); @@ -730,6 +747,50 @@ public class ResourceProviderDstu2Test extends BaseJpaDstu2Test { } } + @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"; @@ -1635,84 +1696,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-base/src/test/resources/META-INF/persistence.xml b/hapi-fhir-jpaserver-base/src/test/resources/META-INF/persistence.xml index 6feb16c727e..897f9420809 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,16 +21,13 @@ 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.SubscriptionFlaggedResource ca.uhn.fhir.jpa.entity.TagDefinition 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/pom.xml b/hapi-fhir-jpaserver-example/pom.xml index 8edd2bf9915..84d895fd8b8 100644 --- a/hapi-fhir-jpaserver-example/pom.xml +++ b/hapi-fhir-jpaserver-example/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir - 1.2 + 1.3-SNAPSHOT ../pom.xml @@ -31,33 +31,33 @@ 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-example/src/main/resources/META-INF/fhirtest_persistence.xml b/hapi-fhir-jpaserver-example/src/main/resources/META-INF/fhirtest_persistence.xml index 6ec380207c0..29aef2bb642 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,8 @@ 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.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 8e993d11811..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. --> - + @@ -40,7 +40,7 @@ - + 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-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..ed759870e4e 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,11 +20,13 @@ 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.SubscriptionFlaggedResource ca.uhn.fhir.jpa.entity.TagDefinition 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/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 @@ + + + + + + + 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-dstu/src/test/java/ca/uhn/fhir/rest/param/DateRangeParamTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/param/DateRangeParamTest.java index 105d22e155b..97553951cba 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/param/DateRangeParamTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/param/DateRangeParamTest.java @@ -10,12 +10,16 @@ import java.util.List; import org.junit.Test; +import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; +import ca.uhn.fhir.model.primitive.DateTimeDt; +import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.rest.method.QualifiedParamList; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; public class DateRangeParamTest { private static SimpleDateFormat ourFmt; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DateRangeParamTest.class); static { ourFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSS"); @@ -25,6 +29,31 @@ public class DateRangeParamTest { return new DateRangeParam(new DateParam(theString)); } + @Test + public void testRange() { + InstantDt start = new InstantDt("2015-09-23T07:43:34.811-04:00"); + InstantDt end = new InstantDt("2015-09-23T07:43:34.899-04:00"); + DateParam lowerBound = new DateParam(QuantityCompararatorEnum.GREATERTHAN, start.getValue()); + DateParam upperBound = new DateParam(QuantityCompararatorEnum.LESSTHAN, end.getValue()); + assertEquals(QuantityCompararatorEnum.GREATERTHAN, lowerBound.getComparator()); + assertEquals(QuantityCompararatorEnum.LESSTHAN, upperBound.getComparator()); + + /* + * When DateParam (which extends DateTimeDt) gets passed in, make sure we preserve the + * comparators.. + */ + DateRangeParam param = new DateRangeParam(lowerBound, upperBound); + ourLog.info(param.toString()); + assertEquals(QuantityCompararatorEnum.GREATERTHAN, param.getLowerBound().getComparator()); + assertEquals(QuantityCompararatorEnum.LESSTHAN, param.getUpperBound().getComparator()); + + param = new DateRangeParam(new DateTimeDt(lowerBound.getValue()), new DateTimeDt(upperBound.getValue())); + ourLog.info(param.toString()); + assertEquals(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, param.getLowerBound().getComparator()); + assertEquals(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS, param.getUpperBound().getComparator()); + + } + @Test public void testAddAnd() { assertEquals(1, new DateAndListParam().addAnd(new DateOrListParam()).getValuesAsQueryTokens().size()); 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-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/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/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/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/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..595ac70b4ed 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 @@ -111,12 +104,10 @@ org.springframework spring-webmvc - ${spring_version} org.springframework spring-context - ${spring_version} xml-apis @@ -127,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 6feb16c727e..cdfcfad2d68 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,13 +21,15 @@ 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.SubscriptionFlaggedResource ca.uhn.fhir.jpa.entity.TagDefinition false - + 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/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 - 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 @@ -505,6 +503,11 @@ spring-core ${spring_version} + + org.springframework.data + spring-data-jpa + 1.9.0.RELEASE + org.springframework spring-orm @@ -525,6 +528,11 @@ spring-web ${spring_version} + + org.springframework + spring-webmvc + ${spring_version} + org.thymeleaf thymeleaf @@ -541,6 +549,16 @@ + + de.juplo + hibernate4-maven-plugin + 1.1.0 + + + org.apache.felix + maven-bundle-plugin + 2.5.4 + org.apache.maven.plugins maven-antrun-plugin @@ -585,11 +603,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 @@ -1218,7 +1246,6 @@ org.apache.maven.plugins maven-gpg-plugin - ${maven_gpg_plugin_version} sign-artifacts @@ -1290,7 +1317,7 @@ hapi-fhir-cli hapi-fhir-dist examples - + 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 diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 349eaa2d071..03e8ed1806d 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -6,6 +6,50 @@ HAPI FHIR Changelog + + + 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) + + + 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. + + + Support AND/OR on _id search parameter in JPA + + + 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! + + + 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. + +
    JPA server now validates QuestionnaireAnswers for conformance to their respective Questionnaire