All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.gematik.test.tiger.lib.rbel.RbelMessageValidator Maven / Gradle / Ivy

/*
 * Copyright 2024 gematik GmbH
 *
 * 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.
 *
 */

package de.gematik.test.tiger.lib.rbel;

import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import de.gematik.rbellogger.data.RbelElement;
import de.gematik.rbellogger.data.facet.*;
import de.gematik.rbellogger.util.RbelPathExecutor;
import de.gematik.rbellogger.writer.RbelContentType;
import de.gematik.test.tiger.LocalProxyRbelMessageListener;
import de.gematik.test.tiger.RbelLoggerWriter;
import de.gematik.test.tiger.common.config.TigerConfigurationKeys;
import de.gematik.test.tiger.common.config.TigerTypedConfigurationKey;
import de.gematik.test.tiger.common.jexl.TigerJexlContext;
import de.gematik.test.tiger.common.jexl.TigerJexlExecutor;
import de.gematik.test.tiger.lib.TigerDirector;
import de.gematik.test.tiger.lib.TigerLibraryException;
import de.gematik.test.tiger.lib.enums.ModeType;
import de.gematik.test.tiger.lib.json.JsonChecker;
import de.gematik.test.tiger.lib.json.JsonSchemaChecker;
import de.gematik.test.tiger.proxy.TigerProxy;
import de.gematik.test.tiger.testenvmgr.TigerTestEnvMgr;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.xml.transform.Source;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.commons.collections4.iterators.ReverseListIterator;
import org.apache.commons.lang3.StringUtils;
import org.awaitility.core.ConditionTimeoutException;
import org.jetbrains.annotations.NotNull;
import org.xmlunit.builder.DiffBuilder;
import org.xmlunit.builder.Input;
import org.xmlunit.diff.ComparisonResult;
import org.xmlunit.diff.ComparisonType;
import org.xmlunit.diff.Diff;
import org.xmlunit.diff.Difference;

@SuppressWarnings("unused")
@Slf4j
public class RbelMessageValidator {

  public static final String RBEL_NAMESPACE = "rbel";
  public static final String FOUND_IN_MESSAGES = "' found in messages";

  private static final List EMPTY_PATH = List.of("", "/");
  public static final TigerTypedConfigurationKey RBEL_REQUEST_TIMEOUT =
      new TigerTypedConfigurationKey<>("tiger.rbel.request.timeout", Integer.class, 5);

  private static final Map> DIFF_OPTIONS = new HashMap<>();

  static {
    DIFF_OPTIONS.put("nocomment", DiffBuilder::ignoreComments);
    DIFF_OPTIONS.put("txtignoreempty", DiffBuilder::ignoreElementContentWhitespace);
    DIFF_OPTIONS.put("txttrim", DiffBuilder::ignoreWhitespace);
    DIFF_OPTIONS.put("txtnormalize", DiffBuilder::normalizeWhitespace);
  }

  private static RbelMessageValidator instance;

  public static RbelMessageValidator getInstance() {
    // In unit tests when we reset the tiger test environment ( see
    // de.gematik.test.tiger.lib.TigerDirector.testUninitialize )
    // we set the instance to null, to force the recreation of the RbelMessageValidator. Otherwise
    // the instance will keep references
    // to a discarded tigerTestEnvMgr and tigerProxy and not see the messages of the current
    // environment.
    synchronized (RbelMessageValidator.class) {
      if (instance == null) { // some other thread might be
        instance = new RbelMessageValidator();
      }
      // in case some other entity instantiated a RbelMessageValidator
      instance.registerJexlToolbox();
    }
    return instance;
  }

  @VisibleForTesting
  public static synchronized void clearInstance() {
    instance = null;
    TigerJexlExecutor.deregisterNamespace(RBEL_NAMESPACE);
  }

  private final TigerTestEnvMgr tigerTestEnvMgr;
  private final TigerProxy tigerProxy;

  @Getter(AccessLevel.PRIVATE)
  private final LocalProxyRbelMessageListener localProxyRbelMessageListener;

  @Setter @Getter protected RbelElement currentRequest;

  // contains either currentRequest or currentResponse
  private RbelElement lastFoundMessage;

  @Setter(AccessLevel.PROTECTED)
  @Getter
  protected RbelElement currentResponse;

  private RbelMessageValidator() {
    this(
        TigerDirector.getTigerTestEnvMgr(),
        TigerDirector.getTigerTestEnvMgr().getLocalTigerProxyOrFail());
  }

  /**
   * @deprecated This constructor is due to be removed. Please use the constructor with the
   *     additional parameter instead.
   */
  @SuppressWarnings("java:S1133")
  @Deprecated(forRemoval = true)
  public RbelMessageValidator(TigerTestEnvMgr tigerTestEnvMgr, TigerProxy tigerProxy) {
    this(tigerTestEnvMgr, tigerProxy, LocalProxyRbelMessageListener.getInstance());
  }

  public RbelMessageValidator(
      TigerTestEnvMgr tigerTestEnvMgr,
      TigerProxy tigerProxy,
      LocalProxyRbelMessageListener localProxyRbelMessageListener) {
    registerJexlToolbox();
    this.tigerTestEnvMgr = tigerTestEnvMgr;
    this.tigerProxy = tigerProxy;
    this.localProxyRbelMessageListener = localProxyRbelMessageListener;
  }

  private void registerJexlToolbox() {
    TigerJexlExecutor.registerAdditionalNamespace(RBEL_NAMESPACE, new JexlToolbox());
  }

  public List getRbelMessages() {
    tigerProxy.waitForAllCurrentMessagesToBeParsed();
    return localProxyRbelMessageListener.getValidatableRbelMessages().stream().toList();
  }

  public void clearRbelMessages() {
    localProxyRbelMessageListener.clearValidatableRbelMessages();
  }

  @SuppressWarnings("java:S1135")
  public void filterRequestsAndStoreInContext(final RequestParameter requestParameter) {
    RbelElement message = tryFindMessageByDescription(requestParameter);
    if (message.hasFacet(RbelRequestFacet.class)) {
      storeRequestAndWaitForAndStoreResponse(message);
    } else if (message.hasFacet(RbelResponseFacet.class)) {
      storeResponseAndSearchAndStoreRequest(message);
    } else {
      log.atInfo()
          // TODO TGR-1528 use element short description mechanism instead of getRawStringContent
          .addArgument(message::getRawStringContent)
          .log("Found message that is neither request nor response:\n\n{}");
    }
  }

  private RbelElement tryFindMessageByDescription(RequestParameter requestParameter) {
    try {
      lastFoundMessage = findMessageByDescription(requestParameter);
      return lastFoundMessage;
    } catch (AssertionError e) {
      clearCurrentMessages();
      throw e;
    }
  }

  @VisibleForTesting
  public void clearCurrentMessages() {
    setCurrentRequest(null);
    setCurrentResponse(null);
    lastFoundMessage = null;
  }

  @SuppressWarnings("java:S1135")
  private void storeRequestAndWaitForAndStoreResponse(RbelElement request) {
    setCurrentRequest(request);
    setCurrentResponse(null);
    if (request
        .getFacet(RbelRequestFacet.class)
        .filter(RbelRequestFacet::isResponseRequired)
        .isPresent()) {
      try {
        final int requestTimeout = RBEL_REQUEST_TIMEOUT.getValueOrDefault();
        await("Waiting for matching request")
            .atMost(requestTimeout, TimeUnit.SECONDS)
            .pollInterval(500, TimeUnit.MILLISECONDS)
            .until(
                () ->
                    tigerTestEnvMgr.isShouldAbortTestExecution()
                        || findAndStoreCorrespondingResponse(request));
        if (tigerTestEnvMgr.isShouldAbortTestExecution()) {
          throw new AssertionError("User aborted test run");
        }
      } catch (final ConditionTimeoutException cte) {
        log.atError()
            // TODO TGR-1528 use element short description mechanism instead of getRawStringContent
            .addArgument(request::getRawStringContent)
            .log("Missing response to filtered request!\n\n{}");
        throw new TigerLibraryException("Missing response to filtered request!", cte);
      }
    }
  }

  @SuppressWarnings("java:S1135")
  private void storeResponseAndSearchAndStoreRequest(RbelElement response) {
    setCurrentResponse(response);
    if (!findAndStoreCorrespondingRequest(response)) {
      setCurrentRequest(response);
      log.atInfo()
          // TODO TGR-1528 use element short description mechanism instead of getRawStringContent
          .addArgument(response::getRawStringContent)
          .log("Missing request to filtered response!\n\n{}");
    }
  }

  private boolean findAndStoreCorrespondingResponse(RbelElement message) {
    return findAndStoreCorrespondingOtherMessage(
        message,
        RbelResponseFacet.class,
        TracingMessagePairFacet::getResponse,
        this::setCurrentResponse);
  }

  private boolean findAndStoreCorrespondingRequest(RbelElement message) {
    return findAndStoreCorrespondingOtherMessage(
        message,
        RbelRequestFacet.class,
        TracingMessagePairFacet::getRequest,
        this::setCurrentRequest);
  }

  private boolean findAndStoreCorrespondingOtherMessage(
      RbelElement message,
      Class otherMessageFacet,
      Function getOtherMessageFromPair,
      Consumer storeOtherMessage) {
    return message.getFacet(TracingMessagePairFacet.class).stream()
        .map(getOtherMessageFromPair)
        .filter(msg -> msg.hasFacet(otherMessageFacet))
        .anyMatch(
            msg -> {
              storeOtherMessage.accept(msg);
              return true;
            });
  }

  public RbelElement waitForMessageToBePresent(final RequestParameter requestParameter) {
    return findMessageByDescription(requestParameter);
  }

  protected RbelElement findMessageByDescription(final RequestParameter requestParameter) {
    final int waitsec = RBEL_REQUEST_TIMEOUT.getValueOrDefault();

    Optional initialElement = getInitialElement(requestParameter);

    final AtomicReference candidate = new AtomicReference<>();
    try {
      await("Waiting for matching request")
          .atMost(waitsec, TimeUnit.SECONDS)
          .pollDelay(0, TimeUnit.SECONDS)
          .pollInterval(400, TimeUnit.MILLISECONDS)
          .until(
              () -> {
                if (tigerTestEnvMgr.isShouldAbortTestExecution()) {
                  return true;
                }
                final Optional found =
                    filterRequests(requestParameter, initialElement);
                found.ifPresent(candidate::set);
                return found.isPresent();
              });
      if (tigerTestEnvMgr.isShouldAbortTestExecution()) {
        throw new AssertionError("User aborted test run");
      }
    } catch (final ConditionTimeoutException cte) {
      log.error("Didn't find any matching messages!");
      printAllPathsOfMessages(getRbelMessages());
      if (requestParameter.getPath() == null) {
        throw new AssertionError(
            String.format(
                "No request with matching rbelPath '%s%s",
                requestParameter.getRbelPath(), FOUND_IN_MESSAGES));
      } else if (requestParameter.getRbelPath() == null) {
        throw new AssertionError(
            String.format(
                "No request with path '%s%s", requestParameter.getPath(), FOUND_IN_MESSAGES));
      } else {
        throw new AssertionError(
            String.format(
                "No request with path '%s' and rbelPath '%s' matching '%s%s",
                requestParameter.getPath(),
                requestParameter.getRbelPath(),
                StringUtils.abbreviate(requestParameter.getValue(), 300),
                FOUND_IN_MESSAGES));
      }
    }
    return candidate.get();
  }

  private Optional getInitialElement(RequestParameter requestParameter) {
    var validatableRbelMessages = localProxyRbelMessageListener.getValidatableRbelMessages();
    if (requestParameter.isStartFromLastMessage()) {
      var markerMessage =
          requestParameter.isRequireRequestMessage() ? currentRequest : lastFoundMessage;
      return validatableRbelMessages.stream()
          .dropWhile(msg -> msg != markerMessage)
          .skip(1)
          .findFirst();
    } else if (requestParameter.isRequireNewMessage() && !validatableRbelMessages.isEmpty()) {
      return Optional.ofNullable(validatableRbelMessages.getLast());
    } else {
      return Optional.empty();
    }
  }

  protected Optional filterRequests(
      final RequestParameter requestParameter, Optional startFromMessageInclusively) {
    List msgs =
        getRbelElementsOptionallyFromGivenMessageInclusively(startFromMessageInclusively);
    final String hostFilter = TigerConfigurationKeys.REQUEST_FILTER_HOST.getValueOrDefault();
    final String methodFilter = TigerConfigurationKeys.REQUEST_FILTER_METHOD.getValueOrDefault();

    List candidateMessages =
        getCandidateMessages(requestParameter, msgs, hostFilter, methodFilter);
    if (candidateMessages.isEmpty()) {
      return Optional.empty();
    }

    if (StringUtils.isEmpty(requestParameter.getRbelPath())) {
      if (candidateMessages.size() > 1) {
        log.atWarn()
            .addArgument(() -> requestParameter.isFilterPreviousRequest() ? "last" : "first")
            .log(
                "Found more then one candidate message. Returning {} message. This may not be"
                    + " deterministic!");
        printAllPathsOfMessages(candidateMessages);
      }
      if (requestParameter.isFilterPreviousRequest()) {
        return Optional.of(candidateMessages.get(candidateMessages.size() - 1));
      } else {
        return Optional.of(candidateMessages.get(0));
      }
    }

    if (requestParameter.isFilterPreviousRequest()) {
      candidateMessages = Lists.reverse(candidateMessages);
    }

    return filterMatchingCandidateMessages(requestParameter, candidateMessages);
  }

  private List getRbelElementsOptionallyFromGivenMessageInclusively(
      Optional startFromMessageExclusively) {
    List msgs = getRbelMessages();
    if (startFromMessageExclusively.isPresent()) {
      int idx = -1;
      for (var i = 0; i < msgs.size(); i++) {
        if (msgs.get(i) == startFromMessageExclusively.get()) {
          idx = i;
          break;
        }
      }
      if (idx > 0) {
        msgs = new ArrayList<>(msgs.subList(idx, msgs.size()));
      }
    }
    return msgs;
  }

  @NotNull
  private List getCandidateMessages(
      RequestParameter requestParameter,
      List msgs,
      String hostFilter,
      String methodFilter) {
    return msgs.stream()
        .filter(
            el ->
                !requestParameter.isRequireRequestMessage() || el.hasFacet(RbelRequestFacet.class))
        .filter(req -> doesPathOfMessageMatch(req, requestParameter.getPath()))
        .filter(req -> hostFilter == null || hostFilter.isEmpty() || doesHostMatch(req, hostFilter))
        .filter(
            req ->
                methodFilter == null
                    || methodFilter.isEmpty()
                    || doesMethodMatch(req, methodFilter))
        .toList();
  }

  @NotNull
  private Optional filterMatchingCandidateMessages(
      RequestParameter requestParameter, List candidateMessages) {
    for (final RbelElement candidateMessage : candidateMessages) {
      final List pathExecutionResult =
          new RbelPathExecutor<>(candidateMessage, requestParameter.getRbelPath()).execute();
      if (pathExecutionResult.isEmpty()) {
        continue;
      }
      if (StringUtils.isEmpty(requestParameter.getValue())) {
        return Optional.of(candidateMessage);
      } else {
        final String content =
            pathExecutionResult.stream()
                .map(RbelMessageValidator::getValueOrContentString)
                .map(String::trim)
                .collect(Collectors.joining());
        try {
          if (content.equals(requestParameter.getValue())
              || content.matches(requestParameter.getValue())
              || Pattern.compile(requestParameter.getValue(), Pattern.DOTALL)
                  .matcher(content)
                  .matches()) {
            return Optional.of(candidateMessage);
          } else {
            log.atInfo()
                .addArgument(() -> StringUtils.abbreviate(content, 300))
                .addArgument(() -> StringUtils.abbreviate(requestParameter.getValue(), 300))
                .log("Found rbel node but \n'{}' didnt match\n'{}'");
          }
        } catch (final Exception ex) {
          log.error(
              "Failure while trying to apply regular expression '{}'!",
              requestParameter.getValue(),
              ex);
        }
      }
    }
    return Optional.empty();
  }

  public boolean doesPathOfMessageMatch(final RbelElement req, final String path) {
    if (path == null) {
      return true;
    }
    try {
      final URI uri =
          new URI(
              req.getFacet(RbelHttpRequestFacet.class)
                  .map(RbelHttpRequestFacet::getPath)
                  .map(RbelMessageValidator::getValueOrContentString)
                  .orElse(""));
      boolean match = doesItMatch(uri.getPath(), path);
      if (!match && EMPTY_PATH.contains(path) && EMPTY_PATH.contains(uri.getPath())) {
        match = true;
      }
      return match;
    } catch (final URISyntaxException e) {
      return false;
    }
  }

  public boolean doesHostMatch(final RbelElement req, final String hostFilter) {
    val host =
        req.getFacet(RbelTcpIpMessageFacet.class)
            .flatMap(e -> RbelHostnameFacet.tryToExtractServerName(e.getReceiver()))
            .orElse("");

    return doesItMatch(host, hostFilter);
  }

  public boolean doesMethodMatch(final RbelElement req, final String method) {
    final String reqMethod =
        req.getFacet(RbelHttpRequestFacet.class)
            .map(RbelHttpRequestFacet::getMethod)
            .map(RbelElement::getRawStringContent)
            .map(String::toUpperCase)
            .orElse("");
    return doesItMatch(reqMethod, method);
  }

  private boolean doesItMatch(final String toTest, String matchingString) {
    try {
      return StringUtils.equals(toTest, matchingString) || toTest.matches(matchingString);
    } catch (PatternSyntaxException rte) {
      log.error("Probable error while parsing regex!", rte);
      return false;
    }
  }

  public void assertAttributeOfCurrentResponseMatches(
      final String rbelPath, final String value, boolean shouldMatch) {
    RbelMessageNodeElementMatchExecutor.builder()
        .rbelPath(rbelPath)
        .shouldMatch(shouldMatch)
        .oracle(value)
        .elements(findElementsInCurrentResponse(rbelPath))
        .build()
        .execute();
  }

  public void assertAttributeOfCurrentRequestMatches(
      final String rbelPath, final String value, boolean shouldMatch) {
    RbelMessageNodeElementMatchExecutor.builder()
        .rbelPath(rbelPath)
        .shouldMatch(shouldMatch)
        .oracle(value)
        .elements(findElementsInCurrentRequest(rbelPath))
        .build()
        .execute();
  }

  public void assertAttributeOfCurrentResponseMatchesAs(
      String rbelPath, ModeType mode, String oracle, String diffOptionCsv) {
    assertAttributeForMessagesMatchAs(
        mode, oracle, findElementsInCurrentResponse(rbelPath), diffOptionCsv);
  }

  public void assertAttributeOfCurrentRequestMatchesAs(
      String rbelPath, ModeType mode, String oracle) {
    assertAttributeForMessagesMatchAs(mode, oracle, findElementsInCurrentRequest(rbelPath), "");
  }

  public void assertAttributeForMessagesMatchAs(
      ModeType mode, String oracle, List elements, String diffOptionCSV) {
    for (RbelElement element : elements) {
      try {
        switch (mode) {
          case JSON -> new JsonChecker()
              .compareJsonStrings(getAsJsonString(element), oracle, false);
          case XML -> compareXMLStructureOfRbelElement(element, oracle, diffOptionCSV);
          case JSON_SCHEMA -> new JsonSchemaChecker()
              .compareJsonToSchema(getAsJsonString(element), oracle);
        }
        log.debug("Found matching element: \n{}", element.printTreeStructure());
        return;
      } catch (JsonChecker.JsonCheckerMismatchException | AssertionError ignored) {
        // try next
      }
    }
    if (elements.size() == 1) {
      throw new AssertionError(
          String.format(
              """
                      Element value:
                      %s
                      Expected:
                      %s""",
              elements.get(0).getRawStringContent(), oracle));
    } else {
      throw new AssertionError(
          String.format(
              "No matching element for value %s found in list of %d elements! ",
              oracle, elements.size()));
    }
  }

  private String getAsJsonString(RbelElement target) {
    if (target.hasFacet(RbelJsonFacet.class)) {
      return target.getRawStringContent();
    } else if (target.hasFacet(RbelCborFacet.class)) {
      return target
          .getFacet(RbelCborFacet.class)
          .map(RbelCborFacet::getNode)
          .map(JsonNode::toString)
          .orElse("");
    } else {
      throw new AssertionError("Node is neither JSON nor CBOR, can not match with JSON");
    }
  }

  private void printAllPathsOfMessages(final List msgs) {
    long requests =
        msgs.stream().filter(msg -> msg.getFacet(RbelHttpRequestFacet.class).isPresent()).count();
    log.info(
        "Found the following {} messages:\n{} ",
        requests,
        msgs.stream()
            .map(msg -> msg.getFacet(RbelHttpRequestFacet.class))
            .filter(Optional::isPresent)
            .map(Optional::get)
            .map(req -> "=>\t" + req.getPathAsString() + " : " + req.getChildElements())
            .collect(Collectors.joining("\n")));
  }

  public void compareXMLStructure(
      final String test, final String oracle, final List> diffOptions) {
    final ArrayList diffs = new ArrayList<>();
    final Source srcTest = Input.from(test).build();
    final Source srcOracle = Input.from(oracle).build();
    DiffBuilder db = DiffBuilder.compare(srcOracle).withTest(srcTest);
    for (final UnaryOperator src : diffOptions) {
      db = src.apply(db);
    }

    db = db.checkForSimilar();
    db.withDifferenceEvaluator(
        (comparison, outcome) -> {
          if (outcome != ComparisonResult.EQUAL
              && (comparison.getType() == ComparisonType.NAMESPACE_URI
                  || comparison.getType() == ComparisonType.NAMESPACE_PREFIX)) {
            return ComparisonResult.SIMILAR;
          }
          return outcome;
        });

    final Diff diff = db.build();
    assertThat(diff.hasDifferences()).withFailMessage("XML tree mismatch!\n" + diff).isFalse();
  }

  public void compareXMLStructure(final String test, final String oracle) {
    compareXMLStructure(test, oracle, Collections.emptyList());
  }

  @SneakyThrows
  public void compareXMLStructure(
      final String test, final String oracle, final String diffOptionCSV) {
    final List> diffOptions = new ArrayList<>();
    Arrays.stream(diffOptionCSV.split(","))
        .map(String::trim)
        .forEach(
            srcClassId -> {
              assertThat(DIFF_OPTIONS).containsKey(srcClassId);
              diffOptions.add(DIFF_OPTIONS.get(srcClassId));
            });
    compareXMLStructure(test, oracle, diffOptions);
  }

  public void compareXMLStructureOfRbelElement(
      final RbelElement el, final String oracle, final String diffOptionCSV) {
    assertThat(el.hasFacet(RbelXmlFacet.class))
        .withFailMessage("Node " + el.getKey() + " is not XML")
        .isTrue();
    compareXMLStructure(el.getRawStringContent(), oracle, diffOptionCSV);
  }

  public RbelElement findElementInCurrentResponse(final String rbelPath) {
    try {
      assertCurrentResponseFound();
      final List elems = currentResponse.findRbelPathMembers(rbelPath);
      assertThat(elems).withFailMessage("No node matching path '" + rbelPath + "'!").isNotEmpty();
      assertThat(elems)
          .withFailMessage("Expected exactly one match for path '" + rbelPath + "'!")
          .hasSize(1);
      return elems.get(0);
    } catch (final Exception e) {
      throw new AssertionError(
          "Unable to find element in last response for rbel path '"
              + rbelPath
              + printMessageTree(currentResponse));
    }
  }

  private static @NotNull String printMessageTree(RbelElement msg) {
    return "' in message\n " + msg.printTreeStructure();
  }

  private void assertCurrentResponseFound() {
    if (currentResponse == null) {
      throw new AssertionError("No current response message found!");
    }
  }

  public RbelElement findElementInCurrentRequest(final String rbelPath) {
    try {
      assertCurrentRequestFound();
      final List elems = currentRequest.findRbelPathMembers(rbelPath);
      if (elems.size() != 1) {
        log.atWarn()
            .addArgument(rbelPath)
            .addArgument(currentRequest::printTreeStructure)
            .log("Could not find elements {} in message\n {}");
      }
      assertThat(elems).withFailMessage("No node matching path '" + rbelPath + "'!").isNotEmpty();
      assertThat(elems)
          .withFailMessage("Expected exactly one match for path '" + rbelPath + "'!")
          .hasSize(1);
      return elems.get(0);
    } catch (final Exception e) {
      throw new AssertionError(
          "Unable to find element in last request for rbel path '"
              + rbelPath
              + printMessageTree(currentRequest));
    }
  }

  public List findElementsInCurrentResponse(final String rbelPath) {
    assertCurrentResponseFound();
    final List elems = currentResponse.findRbelPathMembers(rbelPath);
    if (elems.isEmpty()) {
      throw new AssertionError(
          "Unable to find element in response for rbel path '"
              + rbelPath
              + printMessageTree(currentResponse));
    }
    return elems;
  }

  public List findElementsInCurrentRequest(final String rbelPath) {
    assertCurrentRequestFound();
    final List elems = currentRequest.findRbelPathMembers(rbelPath);
    if (elems.isEmpty()) {
      throw new AssertionError(
          "Unable to find element in request for rbel path '"
              + rbelPath
              + printMessageTree(currentRequest));
    }
    return elems;
  }

  private void assertCurrentRequestFound() {
    if (currentRequest == null) {
      throw new AssertionError("No current request message found!");
    }
  }

  public void findAnyMessageMatchingAtNode(String rbelPath, String value) {
    if (getRbelMessages().stream()
        .map(
            msg -> {
              List findings = new RbelPathExecutor<>(msg, rbelPath).execute();

              if (findings.isEmpty()) {
                return null;
              } else {
                return getValueOrContentString(findings.get(0));
              }
            })
        .filter(Objects::nonNull)
        .filter(msg -> msg.equals(value))
        .findAny()
        .isEmpty()) {
      throw new AssertionError(
          "No message with matching value '" + value + "' at path '" + rbelPath + "'");
    }
  }

  public void findLastRequest() {
    final Iterator descendingIterator = new ReverseListIterator<>(getRbelMessages());
    final RbelElement lastRequest =
        StreamSupport.stream(
                Spliterators.spliteratorUnknownSize(descendingIterator, Spliterator.ORDERED), false)
            .filter(msg -> msg.hasFacet(RbelRequestFacet.class))
            .findFirst()
            .orElseThrow(() -> new TigerLibraryException("No Request found."));
    setCurrentRequest(lastRequest);
    setCurrentResponse(
        lastRequest
            .getFacet(TracingMessagePairFacet.class)
            .map(TracingMessagePairFacet::getResponse)
            .orElse(null));
  }

  public void readTgrFile(String filePath) {
    List readElements = tigerProxy.readTrafficFromTgrFile(filePath);
    readElements.forEach(localProxyRbelMessageListener::triggerNewReceivedMessage);
  }

  public class JexlToolbox {

    public String currentResponseAsString(final String rbelPath) {
      return findElementInCurrentResponse(rbelPath).getRawStringContent();
    }

    public String currentResponseAsString() {
      return currentResponse.getRawStringContent();
    }

    public RbelElement currentResponse(final String rbelPath) {
      return findElementInCurrentResponse(rbelPath);
    }

    public String currentRequestAsString(final String rbelPath) {
      return getValueOrContentString(findElementInCurrentRequest(rbelPath));
    }

    public String currentRequestAsString() {
      return currentRequest.getRawStringContent();
    }

    public RbelElement currentRequest(final String rbelPath) {
      return findElementInCurrentRequest(rbelPath);
    }

    public RbelElement lastResponse() {
      return lastMessageMatching(
          msg ->
              msg.hasFacet(RbelResponseFacet.class) || msg.hasFacet(RbelHttpResponseFacet.class));
    }

    public String lastResponseAsString() {
      return Optional.ofNullable(lastResponse())
          .map(RbelElement::getRawStringContent)
          .orElseThrow(NoSuchElementException::new);
    }

    public RbelElement lastRequest() {
      return lastMessageMatching(
          msg -> msg.hasFacet(RbelRequestFacet.class) || msg.hasFacet(RbelHttpRequestFacet.class));
    }

    private RbelElement lastMessageMatching(Predicate testMessage) {
      final Iterator backwardsIterator =
          localProxyRbelMessageListener.getValidatableRbelMessages().descendingIterator();
      while (backwardsIterator.hasNext()) {
        final RbelElement element = backwardsIterator.next();
        if (testMessage.test(element)) {
          return element;
        }
      }
      throw new NoSuchElementException();
    }

    public String lastRequestAsString() {
      return Optional.ofNullable(lastRequest())
          .map(RbelElement::getRawStringContent)
          .orElseThrow(NoSuchElementException::new);
    }

    public String getValueAtLocationAsString(RbelElement element, String rbelPath) {
      return element
          .findElement(rbelPath)
          .flatMap(el -> el.getFacet(RbelValueFacet.class))
          .map(RbelValueFacet::getValue)
          .map(Object::toString)
          .orElseThrow(
              () ->
                  new NoSuchElementException(
                      "Unable to find a matching element for '" + rbelPath + "'"));
    }

    /**
     * Encodes the string explicitly in the given content type.
     *
     * 

When used inline in a JEXL expression, the valueToEncode must be the raw string output * from either `getValue()` or read directly from `file()`. * * @param valueToEncode the string to encode * @param contentType a string matching one of the {@link RbelContentType}s enum values * @return the encoded string. */ public String encodeAs(String valueToEncode, String contentType) { val encodeAs = RbelContentType.seekValueFor(contentType); RbelLoggerWriter rbelLoggerWriter = new RbelLoggerWriter(); RbelElement toEncode = rbelLoggerWriter.getRbelConverter().convertElement(valueToEncode, null); return rbelLoggerWriter .getRbelWriter() .serializeWithEnforcedContentType(toEncode, encodeAs, new TigerJexlContext()) .getContentAsString(); } } public static String getValueOrContentString(RbelElement elem) { return elem.printValue().orElseGet(elem::getRawStringContent); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy