Example #1
0
 private void writeOptionalTagWithTextNode(
     XMLStreamWriter theEventWriter, String theTagName, InstantDt theInstantDt)
     throws XMLStreamException {
   if (theInstantDt.getValue() != null) {
     theEventWriter.writeStartElement(theTagName);
     theEventWriter.writeCharacters(theInstantDt.getValueAsString());
     theEventWriter.writeEndElement();
   }
 }
  @Transactional(propagation = Propagation.REQUIRED)
  @Override
  public List<IResource> transaction(
      RequestDetails theRequestDetails, List<IResource> theResources) {
    ourLog.info("Beginning transaction with {} resources", theResources.size());

    // Notify interceptors
    ActionRequestDetails requestDetails = new ActionRequestDetails(null, null);
    notifyInterceptors(RestOperationTypeEnum.TRANSACTION, requestDetails);

    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()
          && !isPlaceholder(res.getId())) {
        res.setId(new IdDt(toResourceName(res.getClass()), res.getId().getIdPart()));
      }

      /*
       * Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness
       */
      if (isPlaceholder(res.getId())) {
        if (!allIds.add(res.getId())) {
          throw new InvalidRequestException(
              "Transaction bundle contains multiple resources with ID: " + res.getId());
        }
      } else 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);

    Date updateTime = new Date();
    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(
                        BaseHapiFhirSystemDao.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(
                          BaseHapiFhirSystemDao.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(
                      BaseHapiFhirSystemDao.class,
                      "transactionOperationWithMultipleMatchFailure",
                      nextResouceOperationIn.name(),
                      matchUrl,
                      candidateMatches.size()));
        }
      } else if (nextId.isEmpty() || isPlaceholder(nextId)) {
        entity = null;
      } else {
        entity = tryToLoadEntity(nextId);
      }

      BundleEntryTransactionMethodEnum nextResouceOperationOut;
      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) {
          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, false);
              persistedResources.add(null);
              retVal.add(existing);
              continue;
            }
            if (candidateMatches.size() > 1) {
              throw new InvalidRequestException(
                  getContext()
                      .getLocalizer()
                      .getMessage(
                          BaseHapiFhirSystemDao.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 (isPlaceholder(nextId)) {
            // nextId = new IdDt(resourceName, nextId.getIdPart());
            ourLog.info("Transaction resource ID[{}] has been assigned new ID[{}]", nextId, newId);
            idConversions.put(nextId, newId);
            idConversions.put(new IdDt(resourceName + "/" + nextId.getValue()), 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 = updateTime;
        ResourceMetadataKeyEnum.DELETED_AT.put(resource, new InstantDt(deletedTimestampOrNull));
      }

      updateEntity(resource, table, table.getId() != null, deletedTimestampOrNull, updateTime);
    }

    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");

    return retVal;
  }