Esempio n. 1
0
 private static void handleTransactionCreateOrUpdateOutcome(
     Map<IdType, IdType> idSubstitutions,
     Map<IdType, DaoMethodOutcome> idToPersistedOutcome,
     IdType nextResourceId,
     DaoMethodOutcome outcome,
     BundleEntryComponent newEntry,
     String theResourceType,
     IBaseResource theRes) {
   IdType newId = (IdType) outcome.getId().toUnqualifiedVersionless();
   IdType 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.
        */
       idSubstitutions.put(new IdType(theResourceType + '/' + resourceId.getValue()), newId);
     }
   }
   idToPersistedOutcome.put(newId, outcome);
   if (outcome.getCreated().booleanValue()) {
     newEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_201_CREATED));
   } else {
     newEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_200_OK));
   }
   newEntry.getResponse().setLocation(outcome.getId().toUnqualified().getValue());
   newEntry.getResponse().setEtag(outcome.getId().getVersionIdPart());
   newEntry.getResponse().setLastModified(((Resource) theRes).getMeta().getLastUpdated());
 }
 protected List<String> toNameList(Bundle resp) {
   List<String> names = new ArrayList<String>();
   for (BundleEntryComponent next : resp.getEntry()) {
     Patient nextPt = (Patient) next.getResource();
     String nextStr =
         nextPt.getName().size() > 0
             ? nextPt.getName().get(0).getGivenAsSingleString()
                 + " "
                 + nextPt.getName().get(0).getFamily()
             : "";
     if (isNotBlank(nextStr)) {
       names.add(nextStr);
     }
   }
   return names;
 }
Esempio n. 3
0
 private String extractTransactionUrlOrThrowException(
     BundleEntryComponent nextEntry, HTTPVerb verb) {
   String url = nextEntry.getRequest().getUrl();
   if (isBlank(url)) {
     throw new InvalidRequestException(
         getContext()
             .getLocalizer()
             .getMessage(BaseHapiFhirSystemDao.class, "transactionMissingUrl", verb.name()));
   }
   return url;
 }
Esempio n. 4
0
 @Override
 public boolean supportsSystem(String system) throws TerminologyServiceException {
   if (codeSystems.containsKey(system)) return true;
   else if (nonSupportedCodeSystems.contains(system)) return false;
   else if (system.startsWith("http://example.org")
       || system.startsWith("http://acme.com")
       || system.startsWith("http://hl7.org/fhir/valueset-")
       || system.startsWith("urn:oid:")) return false;
   else {
     if (noTerminologyServer) return false;
     if (bndCodeSystems == null) {
       try {
         log("Terminology server: Check for supported code systems for " + system);
         bndCodeSystems =
             txServer.fetchFeed(
                 txServer.getAddress()
                     + "/CodeSystem?content=not-present&_summary=true&_count=1000");
       } catch (Exception e) {
         if (canRunWithoutTerminology) {
           noTerminologyServer = true;
           log("==============!! Running without terminology server !!==============");
           return false;
         } else throw new TerminologyServiceException(e);
       }
     }
     if (bndCodeSystems != null) {
       for (BundleEntryComponent be : bndCodeSystems.getEntry()) {
         CodeSystem cs = (CodeSystem) be.getResource();
         if (!codeSystems.containsKey(cs.getUrl())) {
           codeSystems.put(cs.getUrl(), null);
         }
       }
     }
     if (codeSystems.containsKey(system)) return true;
   }
   nonSupportedCodeSystems.add(system);
   return false;
 }
Esempio n. 5
0
 private int toOrder(BundleEntryComponent theO1) {
   int o1 = 0;
   if (theO1.getRequest().getMethodElement().getValue() != null) {
     switch (theO1.getRequest().getMethodElement().getValue()) {
       case DELETE:
         o1 = 1;
         break;
       case POST:
         o1 = 2;
         break;
       case PUT:
         o1 = 3;
         break;
       case GET:
         o1 = 4;
         break;
       case NULL:
         o1 = 0;
         break;
     }
   }
   return o1;
 }
Esempio n. 6
0
  @SuppressWarnings("unchecked")
  private Bundle transaction(
      ServletRequestDetails theRequestDetails, Bundle theRequest, String theActionName) {
    BundleType transactionType = theRequest.getTypeElement().getValue();
    if (transactionType == BundleType.BATCH) {
      return batch(theRequestDetails, theRequest);
    }

    if (transactionType == null) {
      String message =
          "Transactiion Bundle did not specify valid Bundle.type, assuming "
              + BundleType.TRANSACTION.toCode();
      ourLog.warn(message);
      transactionType = BundleType.TRANSACTION;
    }
    if (transactionType != BundleType.TRANSACTION) {
      throw new InvalidRequestException(
          "Unable to process transaction where incoming Bundle.type = " + transactionType.toCode());
    }

    ourLog.info("Beginning {} with {} resources", theActionName, theRequest.getEntry().size());

    long start = System.currentTimeMillis();
    Date updateTime = new Date();

    Set<IdType> allIds = new LinkedHashSet<IdType>();
    Map<IdType, IdType> idSubstitutions = new HashMap<IdType, IdType>();
    Map<IdType, DaoMethodOutcome> idToPersistedOutcome = new HashMap<IdType, DaoMethodOutcome>();

    // Do all entries have a verb?
    for (int i = 0; i < theRequest.getEntry().size(); i++) {
      BundleEntryComponent nextReqEntry = theRequest.getEntry().get(i);
      HTTPVerb verb = nextReqEntry.getRequest().getMethodElement().getValue();
      if (verb == null) {
        throw new InvalidRequestException(
            getContext()
                .getLocalizer()
                .getMessage(
                    BaseHapiFhirSystemDao.class,
                    "transactionEntryHasInvalidVerb",
                    nextReqEntry.getRequest().getMethod(),
                    i));
      }
    }

    /*
     * We want to execute the transaction request bundle elements in the order
     * specified by the FHIR specification (see TransactionSorter) so we save the
     * original order in the request, then sort it.
     *
     * Entries with a type of GET are removed from the bundle so that they
     * can be processed at the very end. We do this because the incoming resources
     * are saved in a two-phase way in order to deal with interdependencies, and
     * we want the GET processing to use the final indexing state
     */
    Bundle response = new Bundle();
    List<BundleEntryComponent> getEntries = new ArrayList<BundleEntryComponent>();
    IdentityHashMap<BundleEntryComponent, Integer> originalRequestOrder =
        new IdentityHashMap<Bundle.BundleEntryComponent, Integer>();
    for (int i = 0; i < theRequest.getEntry().size(); i++) {
      originalRequestOrder.put(theRequest.getEntry().get(i), i);
      response.addEntry();
      if (theRequest.getEntry().get(i).getRequest().getMethodElement().getValue() == HTTPVerb.GET) {
        getEntries.add(theRequest.getEntry().get(i));
      }
    }
    Collections.sort(theRequest.getEntry(), new TransactionSorter());

    List<IIdType> deletedResources = new ArrayList<IIdType>();
    List<DeleteConflict> deleteConflicts = new ArrayList<DeleteConflict>();

    /*
     * Loop through the request and process any entries of type
     * PUT, POST or DELETE
     */
    for (int i = 0; i < theRequest.getEntry().size(); i++) {

      if (i % 100 == 0) {
        ourLog.info("Processed {} non-GET entries out of {}", i, theRequest.getEntry().size());
      }

      BundleEntryComponent nextReqEntry = theRequest.getEntry().get(i);
      Resource res = nextReqEntry.getResource();
      IdType nextResourceId = null;
      if (res != null) {

        nextResourceId = res.getIdElement();

        if (nextResourceId.hasIdPart() == false) {
          if (isNotBlank(nextReqEntry.getFullUrl())) {
            nextResourceId = new IdType(nextReqEntry.getFullUrl());
          }
        }

        if (nextResourceId.hasIdPart()
            && nextResourceId.getIdPart().matches("[a-zA-Z]+\\:.*")
            && !isPlaceholder(nextResourceId)) {
          throw new InvalidRequestException(
              "Invalid placeholder ID found: "
                  + nextResourceId.getIdPart()
                  + " - Must be of the form 'urn:uuid:[uuid]' or 'urn:oid:[oid]'");
        }

        if (nextResourceId.hasIdPart()
            && !nextResourceId.hasResourceType()
            && !isPlaceholder(nextResourceId)) {
          nextResourceId = new IdType(toResourceName(res.getClass()), nextResourceId.getIdPart());
          res.setId(nextResourceId);
        }

        /*
         * Ensure that the bundle doesn't have any duplicates, since this causes all kinds of weirdness
         */
        if (isPlaceholder(nextResourceId)) {
          if (!allIds.add(nextResourceId)) {
            throw new InvalidRequestException(
                getContext()
                    .getLocalizer()
                    .getMessage(
                        BaseHapiFhirSystemDao.class,
                        "transactionContainsMultipleWithDuplicateId",
                        nextResourceId));
          }
        } else if (nextResourceId.hasResourceType() && nextResourceId.hasIdPart()) {
          IdType nextId = nextResourceId.toUnqualifiedVersionless();
          if (!allIds.add(nextId)) {
            throw new InvalidRequestException(
                getContext()
                    .getLocalizer()
                    .getMessage(
                        BaseHapiFhirSystemDao.class,
                        "transactionContainsMultipleWithDuplicateId",
                        nextId));
          }
        }
      }

      HTTPVerb verb = nextReqEntry.getRequest().getMethodElement().getValue();

      String resourceType = res != null ? getContext().getResourceDefinition(res).getName() : null;
      BundleEntryComponent nextRespEntry =
          response.getEntry().get(originalRequestOrder.get(nextReqEntry));

      switch (verb) {
        case POST:
          {
            // CREATE
            @SuppressWarnings("rawtypes")
            IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass());
            res.setId((String) null);
            DaoMethodOutcome outcome;
            outcome = resourceDao.create(res, nextReqEntry.getRequest().getIfNoneExist(), false);
            handleTransactionCreateOrUpdateOutcome(
                idSubstitutions,
                idToPersistedOutcome,
                nextResourceId,
                outcome,
                nextRespEntry,
                resourceType,
                res);
            break;
          }
        case DELETE:
          {
            // DELETE
            String url = extractTransactionUrlOrThrowException(nextReqEntry, verb);
            UrlParts parts = UrlUtil.parseUrl(url);
            ca.uhn.fhir.jpa.dao.IFhirResourceDao<? extends IBaseResource> dao =
                toDao(parts, verb.toCode(), url);
            int status = Constants.STATUS_HTTP_204_NO_CONTENT;
            if (parts.getResourceId() != null) {
              ResourceTable deleted =
                  dao.delete(
                      new IdType(parts.getResourceType(), parts.getResourceId()), deleteConflicts);
              if (deleted != null) {
                deletedResources.add(deleted.getIdDt().toUnqualifiedVersionless());
              }
            } else {
              List<ResourceTable> allDeleted =
                  dao.deleteByUrl(
                      parts.getResourceType() + '?' + parts.getParams(), deleteConflicts);
              for (ResourceTable deleted : allDeleted) {
                deletedResources.add(deleted.getIdDt().toUnqualifiedVersionless());
              }
              if (allDeleted.isEmpty()) {
                status = Constants.STATUS_HTTP_404_NOT_FOUND;
              }
            }

            nextRespEntry.getResponse().setStatus(toStatusString(status));
            break;
          }
        case PUT:
          {
            // UPDATE
            @SuppressWarnings("rawtypes")
            IFhirResourceDao resourceDao = getDaoOrThrowException(res.getClass());

            DaoMethodOutcome outcome;

            String url = extractTransactionUrlOrThrowException(nextReqEntry, verb);

            UrlParts parts = UrlUtil.parseUrl(url);
            if (isNotBlank(parts.getResourceId())) {
              res.setId(new IdType(parts.getResourceType(), parts.getResourceId()));
              outcome = resourceDao.update(res, null, false);
            } else {
              res.setId((String) null);
              outcome =
                  resourceDao.update(res, parts.getResourceType() + '?' + parts.getParams(), false);
            }

            handleTransactionCreateOrUpdateOutcome(
                idSubstitutions,
                idToPersistedOutcome,
                nextResourceId,
                outcome,
                nextRespEntry,
                resourceType,
                res);
            break;
          }
      }
    }

    /*
     * Make sure that there are no conflicts from deletions. E.g. we can't delete something
     * if something else has a reference to it.. Unless the thing that has a reference to it
     * was also deleted as a part of this transaction, which is why we check this now at the
     * end.
     */

    for (Iterator<DeleteConflict> iter = deleteConflicts.iterator(); iter.hasNext(); ) {
      DeleteConflict next = iter.next();
      if (deletedResources.contains(next.getTargetId().toVersionless())) {
        iter.remove();
      }
    }
    validateDeleteConflictsEmptyOrThrowException(deleteConflicts);

    /*
     * Perform ID substitutions and then index each resource we have saved
     */

    FhirTerser terser = getContext().newTerser();
    for (DaoMethodOutcome nextOutcome : idToPersistedOutcome.values()) {
      IBaseResource nextResource = (IBaseResource) nextOutcome.getResource();
      if (nextResource == null) {
        continue;
      }

      List<IBaseReference> allRefs =
          terser.getAllPopulatedChildElementsOfType(nextResource, IBaseReference.class);
      for (IBaseReference nextRef : allRefs) {
        IIdType nextId = nextRef.getReferenceElement();
        if (idSubstitutions.containsKey(nextId)) {
          IdType newId = idSubstitutions.get(nextId);
          ourLog.info(" * Replacing resource ref {} with {}", nextId, newId);
          nextRef.setReference(newId.getValue());
        } else {
          ourLog.debug(" * Reference [{}] does not exist in bundle", nextId);
        }
      }

      IPrimitiveType<Date> deletedInstantOrNull =
          ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) nextResource);
      Date deletedTimestampOrNull =
          deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null;
      updateEntity(
          nextResource,
          nextOutcome.getEntity(),
          false,
          deletedTimestampOrNull,
          true,
          false,
          updateTime);
    }

    myEntityManager.flush();

    /*
     * Double check we didn't allow any duplicates we shouldn't have
     */
    for (BundleEntryComponent nextEntry : theRequest.getEntry()) {
      if (nextEntry.getRequest().getMethodElement().getValue() == HTTPVerb.POST) {
        String matchUrl = nextEntry.getRequest().getIfNoneExist();
        if (isNotBlank(matchUrl)) {
          IFhirResourceDao<?> resourceDao = getDao(nextEntry.getResource().getClass());
          Set<Long> 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?");
          }
        }
      }
    }

    for (IdType next : allIds) {
      IdType replacement = idSubstitutions.get(next);
      if (replacement == null) {
        continue;
      }
      if (replacement.equals(next)) {
        continue;
      }
      ourLog.info(
          "Placeholder resource ID \"{}\" was replaced with permanent ID \"{}\"",
          next,
          replacement);
    }

    /*
     * Loop through the request and process any entries of type GET
     */
    for (int i = 0; i < getEntries.size(); i++) {
      BundleEntryComponent nextReqEntry = getEntries.get(i);
      Integer originalOrder = originalRequestOrder.get(nextReqEntry);
      BundleEntryComponent nextRespEntry = response.getEntry().get(originalOrder);

      ServletSubRequestDetails requestDetails = new ServletSubRequestDetails();
      requestDetails.setServletRequest(theRequestDetails.getServletRequest());
      requestDetails.setRequestType(RequestTypeEnum.GET);
      requestDetails.setServer(theRequestDetails.getServer());

      String url = extractTransactionUrlOrThrowException(nextReqEntry, HTTPVerb.GET);

      int qIndex = url.indexOf('?');
      ArrayListMultimap<String, String> paramValues = ArrayListMultimap.create();
      requestDetails.setParameters(new HashMap<String, String[]>());
      if (qIndex != -1) {
        String params = url.substring(qIndex);
        List<NameValuePair> parameters = translateMatchUrl(params);
        for (NameValuePair next : parameters) {
          paramValues.put(next.getName(), next.getValue());
        }
        for (java.util.Map.Entry<String, Collection<String>> nextParamEntry :
            paramValues.asMap().entrySet()) {
          String[] nextValue =
              nextParamEntry.getValue().toArray(new String[nextParamEntry.getValue().size()]);
          requestDetails.getParameters().put(nextParamEntry.getKey(), nextValue);
        }
        url = url.substring(0, qIndex);
      }

      requestDetails.setRequestPath(url);
      requestDetails.setFhirServerBase(theRequestDetails.getFhirServerBase());

      theRequestDetails.getServer().populateRequestDetailsFromRequestPath(requestDetails, url);
      BaseMethodBinding<?> method =
          theRequestDetails.getServer().determineResourceMethod(requestDetails, url);
      if (method == null) {
        throw new IllegalArgumentException("Unable to handle GET " + url);
      }

      if (isNotBlank(nextReqEntry.getRequest().getIfMatch())) {
        requestDetails.addHeader(Constants.HEADER_IF_MATCH, nextReqEntry.getRequest().getIfMatch());
      }
      if (isNotBlank(nextReqEntry.getRequest().getIfNoneExist())) {
        requestDetails.addHeader(
            Constants.HEADER_IF_NONE_EXIST, nextReqEntry.getRequest().getIfNoneExist());
      }
      if (isNotBlank(nextReqEntry.getRequest().getIfNoneMatch())) {
        requestDetails.addHeader(
            Constants.HEADER_IF_NONE_MATCH, nextReqEntry.getRequest().getIfNoneMatch());
      }

      if (method instanceof BaseResourceReturningMethodBinding) {
        try {
          ResourceOrDstu1Bundle responseData =
              ((BaseResourceReturningMethodBinding) method)
                  .invokeServer(theRequestDetails.getServer(), requestDetails, new byte[0]);
          IBaseResource resource = responseData.getResource();
          if (paramValues.containsKey(Constants.PARAM_SUMMARY)
              || paramValues.containsKey(Constants.PARAM_CONTENT)) {
            resource = filterNestedBundle(requestDetails, resource);
          }
          nextRespEntry.setResource((Resource) resource);
          nextRespEntry.getResponse().setStatus(toStatusString(Constants.STATUS_HTTP_200_OK));
        } catch (NotModifiedException e) {
          nextRespEntry
              .getResponse()
              .setStatus(toStatusString(Constants.STATUS_HTTP_304_NOT_MODIFIED));
        }
      } else {
        throw new IllegalArgumentException("Unable to handle GET " + url);
      }
    }

    long delay = System.currentTimeMillis() - start;
    ourLog.info(theActionName + " completed in {}ms", new Object[] {delay});

    response.setType(BundleType.TRANSACTIONRESPONSE);
    return response;
  }
Esempio n. 7
0
  private Bundle batch(final RequestDetails theRequestDetails, Bundle theRequest) {
    ourLog.info("Beginning batch with {} resources", theRequest.getEntry().size());
    long start = System.currentTimeMillis();

    TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
    txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

    Bundle resp = new Bundle();
    resp.setType(BundleType.BATCHRESPONSE);
    OperationOutcome ooResp = new OperationOutcome();
    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 (final BundleEntryComponent nextRequestEntry : theRequest.getEntry()) {

      TransactionCallback<Bundle> callback =
          new TransactionCallback<Bundle>() {
            @Override
            public Bundle doInTransaction(TransactionStatus theStatus) {
              Bundle subRequestBundle = new Bundle();
              subRequestBundle.setType(BundleType.TRANSACTION);
              subRequestBundle.addEntry(nextRequestEntry);

              Bundle subResponseBundle =
                  transaction(
                      (ServletRequestDetails) theRequestDetails,
                      subRequestBundle,
                      "Batch sub-request");
              return subResponseBundle;
            }
          };

      BaseServerResponseException caughtEx;
      try {
        Bundle nextResponseBundle = txTemplate.execute(callback);
        caughtEx = null;

        BundleEntryComponent 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 (subResponseEntry.getResource() == null) {
          subResponseEntry.setResource(nextResponseBundle.getEntry().get(0).getResource());
        }

      } catch (BaseServerResponseException e) {
        caughtEx = e;
      } catch (Throwable t) {
        ourLog.error("Failure during BATCH sub transaction processing", t);
        caughtEx = new InternalErrorException(t);
      }

      if (caughtEx != null) {
        BundleEntryComponent nextEntry = resp.addEntry();

        OperationOutcome oo = new OperationOutcome();
        oo.addIssue().setSeverity(IssueSeverity.ERROR).setDiagnostics(caughtEx.getMessage());
        nextEntry.setResource(oo);

        BundleEntryResponseComponent nextEntryResp = nextEntry.getResponse();
        nextEntryResp.setStatus(toStatusString(caughtEx.getStatusCode()));
      }
    }

    long delay = System.currentTimeMillis() - start;
    ourLog.info("Batch completed in {}ms", new Object[] {delay});
    ooResp
        .addIssue()
        .setSeverity(IssueSeverity.INFORMATION)
        .setDiagnostics("Batch completed in " + delay + "ms");

    return resp;
  }