FhirSystemDaoDstu1 extends BaseFhirSystemDao<List<IResource>> { @Transactional(propagation = Propagation.REQUIRED) @Override public List<IResource> transaction(List<IResource> theResources) { ourLog.info("Beginning transaction with {} resources", theResources.size()); long start = System.currentTimeMillis(); Set<IdDt> allIds = new HashSet<IdDt>(); for (int i = 0; i < theResources.size(); i++) { IResource res = theResources.get(i); if (res.getId().hasIdPart() && !res.getId().hasResourceType()) { res.setId(new IdDt(toResourceName(res.getClass()), res.getId().getIdPart())); } if (res.getId().hasResourceType() && res.getId().hasIdPart()) { IdDt nextId = res.getId().toUnqualifiedVersionless(); if (!allIds.add(nextId)) { throw new InvalidRequestException("Transaction bundle contains multiple resources with ID: " + nextId); } } } FhirTerser terser = getContext().newTerser(); int creations = 0; int updates = 0; Map<IdDt, IdDt> idConversions = new HashMap<IdDt, IdDt>(); List<ResourceTable> persistedResources = new ArrayList<ResourceTable>(); List<IResource> retVal = new ArrayList<IResource>(); OperationOutcome oo = new OperationOutcome(); retVal.add(oo); for (int resourceIdx = 0; resourceIdx < theResources.size(); resourceIdx++) { IResource nextResource = theResources.get(resourceIdx); IdDt nextId = nextResource.getId(); if (nextId == null) { nextId = new IdDt(); } String resourceName = toResourceName(nextResource); BundleEntryTransactionMethodEnum nextResouceOperationIn = ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.get(nextResource); if (nextResouceOperationIn == null && hasValue(ResourceMetadataKeyEnum.DELETED_AT.get(nextResource))) { nextResouceOperationIn = BundleEntryTransactionMethodEnum.DELETE; } String matchUrl = ResourceMetadataKeyEnum.LINK_SEARCH.get(nextResource); Set<Long> candidateMatches = null; if (StringUtils.isNotBlank(matchUrl)) { candidateMatches = processMatchUrl(matchUrl, nextResource.getClass()); } ResourceTable entity; if (nextResouceOperationIn == BundleEntryTransactionMethodEnum.POST) { entity = null; } else if (nextResouceOperationIn == BundleEntryTransactionMethodEnum.PUT || nextResouceOperationIn == BundleEntryTransactionMethodEnum.DELETE) { if (candidateMatches == null || candidateMatches.size() == 0) { if (nextId == null || StringUtils.isBlank(nextId.getIdPart())) { throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseFhirSystemDao.class, "transactionOperationFailedNoId", nextResouceOperationIn.name())); } entity = tryToLoadEntity(nextId); if (entity == null) { if (nextResouceOperationIn == BundleEntryTransactionMethodEnum.PUT) { ourLog.debug("Attempting to UPDATE resource with unknown ID '{}', will CREATE instead", nextId); } else if (candidateMatches == null) { throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseFhirSystemDao.class, "transactionOperationFailedUnknownId", nextResouceOperationIn.name(), nextId)); } else { ourLog.debug("Resource with match URL [{}] already exists, will be NOOP", matchUrl); persistedResources.add(null); retVal.add(nextResource); continue; } } } else if (candidateMatches.size() == 1) { entity = loadFirstEntityFromCandidateMatches(candidateMatches); } else { throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseFhirSystemDao.class, "transactionOperationWithMultipleMatchFailure", nextResouceOperationIn.name(), matchUrl, candidateMatches.size())); } } else if (nextId.isEmpty()) { entity = null; } else { entity = tryToLoadEntity(nextId); } BundleEntryTransactionMethodEnum nextResouceOperationOut; if (entity == null) { nextResouceOperationOut = BundleEntryTransactionMethodEnum.POST; entity = toEntity(nextResource); if (nextId.isEmpty() == false && nextId.getIdPart().startsWith("cid:")) { ourLog.debug("Resource in transaction has ID[{}], will replace with server assigned ID", nextId.getIdPart()); } else if (nextResouceOperationIn == BundleEntryTransactionMethodEnum.POST) { if (nextId.isEmpty() == false) { ourLog.debug("Resource in transaction has ID[{}] but is marked for CREATE, will ignore ID", nextId.getIdPart()); } if (candidateMatches != null) { 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); persistedResources.add(null); retVal.add(existing); continue; } if (candidateMatches.size() > 1) { throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseFhirSystemDao.class, "transactionOperationWithMultipleMatchFailure", BundleEntryTransactionMethodEnum.POST.name(), matchUrl, candidateMatches.size())); } } } else { createForcedIdIfNeeded(entity, nextId); } myEntityManager.persist(entity); if (entity.getForcedId() != null) { myEntityManager.persist(entity.getForcedId()); } creations++; ourLog.info("Resource Type[{}] with ID[{}] does not exist, creating it", resourceName, nextId); } else { nextResouceOperationOut = nextResouceOperationIn; if (nextResouceOperationOut == null) { nextResouceOperationOut = BundleEntryTransactionMethodEnum.PUT; } updates++; ourLog.info("Resource Type[{}] with ID[{}] exists, updating it", resourceName, nextId); } persistedResources.add(entity); retVal.add(nextResource); ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(nextResource, nextResouceOperationOut); } ourLog.info("Flushing transaction to database"); myEntityManager.flush(); for (int i = 0; i < persistedResources.size(); i++) { ResourceTable entity = persistedResources.get(i); String resourceName = toResourceName(theResources.get(i)); IdDt nextId = theResources.get(i).getId(); IdDt newId; if (entity == null) { newId = retVal.get(i + 1).getId().toUnqualifiedVersionless(); } else { newId = entity.getIdDt().toUnqualifiedVersionless(); } if (nextId == null || nextId.isEmpty()) { ourLog.info("Transaction resource (with no preexisting ID) has been assigned new ID[{}]", nextId, newId); } else { if (nextId.toUnqualifiedVersionless().equals(newId)) { ourLog.info("Transaction resource ID[{}] is being updated", newId); } else { if (!nextId.getIdPart().startsWith("#")) { nextId = new IdDt(resourceName + '/' + nextId.getIdPart()); ourLog.info("Transaction resource ID[{}] has been assigned new ID[{}]", nextId, newId); idConversions.put(nextId, newId); } } } } for (IResource nextResource : theResources) { List<BaseResourceReferenceDt> allRefs = terser.getAllPopulatedChildElementsOfType(nextResource, BaseResourceReferenceDt.class); for (BaseResourceReferenceDt nextRef : allRefs) { IdDt nextId = nextRef.getReference(); if (idConversions.containsKey(nextId)) { IdDt newId = idConversions.get(nextId); ourLog.info(" * Replacing resource ref {} with {}", nextId, newId); nextRef.setReference(newId); } else { ourLog.debug(" * Reference [{}] does not exist in bundle", nextId); } } } ourLog.info("Re-flushing updated resource references and extracting search criteria"); for (int i = 0; i < theResources.size(); i++) { IResource resource = theResources.get(i); ResourceTable table = persistedResources.get(i); if (table == null) { continue; } 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(); ResourceMetadataKeyEnum.DELETED_AT.put(resource, new InstantDt(deletedTimestampOrNull)); } updateEntity(resource, table, table.getId() != null, deletedTimestampOrNull); } long delay = System.currentTimeMillis() - start; ourLog.info("Transaction completed in {}ms with {} creations and {} updates", new Object[] { delay, creations, updates }); oo.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDetails("Transaction completed in " + delay + "ms with " + creations + " creations and " + updates + " updates"); notifyWriteCompleted(); return retVal; }  @Transactional(propagation = Propagation.REQUIRED) @Override List<IResource> transaction(List<IResource> theResources); @Override MetaDt metaGetOperation();  }
@Test public void testPersistWithSimpleLink() { Patient patient = new Patient(); patient.setId(new IdDt("Patient/testPersistWithSimpleLinkP01")); patient.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP01"); patient.addName().addFamily("Tester").addGiven("Joe"); Observation obs = new Observation(); obs.getName().addCoding().setSystem("urn:system").setCode("testPersistWithSimpleLinkO01"); obs.setSubject(new ResourceReferenceDt("Patient/testPersistWithSimpleLinkP01")); ourSystemDao.transaction(Arrays.asList((IResource) patient, obs)); String patientId = (patient.getId().getIdPart()); String obsId = (obs.getId().getIdPart()); IBundleProvider obsResults = ourObservationDao.search(Observation.SP_NAME, new IdentifierDt("urn:system", "testPersistWithSimpleLinkO01")); assertEquals(1, obsResults.size()); IBundleProvider patResults = ourPatientDao.search(Patient.SP_IDENTIFIER, new IdentifierDt("urn:system", "testPersistWithSimpleLinkP01")); assertEquals(1, obsResults.size()); IdDt foundPatientId = patResults.getResources(0, 1).get(0).getId(); ResourceReferenceDt subject = obs.getSubject(); assertEquals(foundPatientId.getIdPart(), subject.getReference().getIdPart()); patient = (Patient) patResults.getResources(0, 1).get(0); obs = (Observation) obsResults.getResources(0, 1).get(0); patient.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP02"); obs.getName().addCoding().setSystem("urn:system").setCode("testPersistWithSimpleLinkO02"); ourSystemDao.transaction(Arrays.asList((IResource) patient, obs)); String patientId2 = (patient.getId().getIdPart()); String patientVersion2 = (patient.getId().getVersionIdPart()); String obsId2 = (obs.getId().getIdPart()); String obsVersion2 = (obs.getId().getVersionIdPart()); assertEquals(patientId, patientId2); assertEquals(patientVersion2, "2"); assertEquals(obsId, obsId2); assertEquals(obsVersion2, "2"); }
@Test public void testPersistWithUnknownId() { Observation obs = new Observation(); obs.getName().addCoding().setSystem("urn:system").setCode("testPersistWithSimpleLinkO01"); obs.setSubject(new ResourceReferenceDt("Patient/999998888888")); try { ourSystemDao.transaction(Arrays.asList((IResource) obs)); } catch (InvalidRequestException e) { assertThat(e.getMessage(), containsString("Resource Patient/999998888888 not found, specified in path: Observation.subject")); } obs = new Observation(); obs.getName().addCoding().setSystem("urn:system").setCode("testPersistWithSimpleLinkO01"); obs.setSubject(new ResourceReferenceDt("Patient/1.2.3.4")); try { ourSystemDao.transaction(Arrays.asList((IResource) obs)); } catch (InvalidRequestException e) { assertThat(e.getMessage(), containsString("Resource Patient/1.2.3.4 not found, specified in path: Observation.subject")); } }
@Test(expected = InvalidRequestException.class) public void testTransactionFailsWithDuplicateIds() { Patient patient1 = new Patient(); patient1.setId(new IdDt("Patient/testTransactionFailsWithDusplicateIds")); patient1.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP01"); Patient patient2 = new Patient(); patient2.setId(new IdDt("Patient/testTransactionFailsWithDusplicateIds")); patient2.addIdentifier().setSystem("urn:system").setValue("testPersistWithSimpleLinkP02"); ourSystemDao.transaction(Arrays.asList((IResource) patient1, patient2)); }
@Test public void testTransactionFromBundle() throws Exception { InputStream bundleRes = FhirSystemDaoDstu1Test.class.getResourceAsStream("/bundle-dstu1.xml"); Bundle bundle = ourFhirContext.newXmlParser().parseBundle(new InputStreamReader(bundleRes)); List<IResource> res = bundle.toListOfResources(); ourSystemDao.transaction(res); Patient p1 = (Patient) res.get(0); String id = p1.getId().getValue(); ourLog.info("ID: {}", id); assertThat(id, not(equalToIgnoringCase("74635"))); assertThat(id, not(equalToIgnoringCase(""))); }
@Test public void testTransactionWithCidIds() throws Exception { List<IResource> res = new ArrayList<IResource>(); Patient p1 = new Patient(); p1.setId("cid:patient1"); p1.addIdentifier().setSystem("system").setValue("testTransactionWithCidIds01"); res.add(p1); Observation o1 = new Observation(); o1.setId("cid:observation1"); o1.getIdentifier().setSystem("system").setValue("testTransactionWithCidIds02"); o1.setSubject(new ResourceReferenceDt("Patient/cid:patient1")); res.add(o1); Observation o2 = new Observation(); o2.setId("cid:observation2"); o2.getIdentifier().setSystem("system").setValue("testTransactionWithCidIds03"); o2.setSubject(new ResourceReferenceDt("Patient/cid:patient1")); res.add(o2); ourSystemDao.transaction(res); assertTrue(p1.getId().getValue(), p1.getId().getIdPart().matches("^[0-9]+$")); assertTrue(o1.getId().getValue(), o1.getId().getIdPart().matches("^[0-9]+$")); assertTrue(o2.getId().getValue(), o2.getId().getIdPart().matches("^[0-9]+$")); assertThat(o1.getSubject().getReference().getValue(), endsWith("Patient/" + p1.getId().getIdPart())); assertThat(o2.getSubject().getReference().getValue(), endsWith("Patient/" + p1.getId().getIdPart())); }
@Test public void testTransactionWithDelete() throws Exception { List<IResource> res; res = new ArrayList<IResource>(); Patient p1 = new Patient(); p1.addIdentifier().setSystem("urn:system").setValue("testTransactionWithDelete"); res.add(p1); Patient p2 = new Patient(); p2.addIdentifier().setSystem("urn:system").setValue("testTransactionWithDelete"); res.add(p2); Patient p3 = new Patient(); p3.addIdentifier().setSystem("urn:system").setValue("testTransactionWithDelete"); res.add(p3); ourSystemDao.transaction(res); IBundleProvider results = ourPatientDao.search(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "testTransactionWithDelete")); assertEquals(3, results.size()); res = new ArrayList<IResource>(); List<IResource> existing = results.getResources(0, 3); p1 = new Patient(); p1.setId(existing.get(0).getId()); ResourceMetadataKeyEnum.DELETED_AT.put(p1, InstantDt.withCurrentTime()); res.add(p1); p2 = new Patient(); p2.setId(existing.get(1).getId()); ResourceMetadataKeyEnum.DELETED_AT.put(p2, InstantDt.withCurrentTime()); res.add(p2); ourSystemDao.transaction(res); IBundleProvider results2 = ourPatientDao.search(Patient.SP_IDENTIFIER, new TokenParam("urn:system", "testTransactionWithDelete")); assertEquals(1, results2.size()); List<IResource> existing2 = results2.getResources(0, 1); assertEquals(existing2.get(0).getId(), existing.get(2).getId()); }