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

us.abstracta.jmeter.javadsl.bridge.serialization.constructs.BridgedObjectConstruct Maven / Gradle / Ivy

Go to download

Module which allows to build simple adapters for JMeter Java DSL in other languages

The newest version!
package us.abstracta.jmeter.javadsl.bridge.serialization.constructs;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.http.entity.ContentType;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.ScalarNode;
import org.yaml.snakeyaml.nodes.SequenceNode;
import us.abstracta.jmeter.javadsl.bridge.serialization.BridgedObjectConstructor;
import us.abstracta.jmeter.javadsl.bridge.serialization.BuilderMethod;
import us.abstracta.jmeter.javadsl.bridge.serialization.TestElementConstructorException;

public class BridgedObjectConstruct extends BaseBridgedObjectConstruct {

  private static final Map, Function> PARSERS = solveParsers();

  private final BridgedObjectConstructor constructor;
  private final String tag;
  private final List builders;

  public BridgedObjectConstruct(BridgedObjectConstructor constructor, String tag,
      List builders) {
    this.constructor = constructor;
    this.tag = tag;
    this.builders = builders;
    builders.sort((b1, b2) -> {
      int ret = compareMoreParametersFirst(b1, b2);
      return ret != 0 ? ret : compareFirstStringParameters(b1, b2);
    });
  }

  private int compareMoreParametersFirst(BuilderMethod b1, BuilderMethod b2) {
    return b2.getParameters().length - b1.getParameters().length;
  }

  // prefer methods with string parameters (JMeter expressions) over specific types
  private int compareFirstStringParameters(BuilderMethod b1, BuilderMethod b2) {
    Parameter[] params1 = b1.getParameters();
    Parameter[] params2 = b2.getParameters();
    for (int i = 0; i < params1.length; i++) {
      if (params1[i].getType().equals(String.class) && !params2[i].getType()
          .equals(String.class)) {
        return -1;
      } else if (params2[i].getType().equals(String.class) && !params1[i].getType()
          .equals(String.class)) {
        return 1;
      }
    }
    return 0;
  }

  private static Map, Function> solveParsers() {
    Map, Function> ret = new HashMap<>();
    ret.put(int.class, Integer::parseInt);
    ret.put(long.class, Long::parseLong);
    ret.put(boolean.class, Boolean::parseBoolean);
    ret.put(double.class, Double::parseDouble);
    ret.put(Duration.class, Duration::parse);
    ret.put(ContentType.class, ContentType::parse);
    ret.put(String.class, s -> s);
    return ret;
  }

  @Override
  public Object construct(Node node) {
    Map nodeProperties = getNodeProperties(node, tag);
    Object ret = buildTestElement(nodeProperties, node);
    buildPropertiesFromList(nodeProperties.remove("_propsList"), ret);
    nodeProperties.forEach((propName, propNode) -> callPropertyMethod(propName, propNode, ret));
    return ret;
  }

  private Object buildTestElement(Map nodeProperties, Node node) {
    for (BuilderMethod m : builders) {
      Map> paramNodes = extractParametersNodes(m, nodeProperties);
      if (allParametersFound(paramNodes)) {
        try {
          paramNodes.keySet().forEach(p -> nodeProperties.remove(p.getName()));
          Object[] args = buildBuilderArguments(paramNodes);
          return m.invoke(args);
        } catch (ReflectiveOperationException e) {
          throw new RuntimeException(e);
        }
      }
    }
    throw new TestElementConstructorException(tag, node, "could not find a proper builder");
  }

  private LinkedHashMap> extractParametersNodes(BuilderMethod m,
      Map properties) {
    return Arrays.stream(m.getParameters())
        .collect(Collectors.toMap(p -> p,
            p -> Optional.ofNullable(properties.get(p.getName())), (u, v) -> u,
            LinkedHashMap::new));
  }

  private boolean allParametersFound(Map> paramNodes) {
    return paramNodes.values().stream().allMatch(Optional::isPresent);
  }

  private Object[] buildBuilderArguments(Map> paramNodes) {
    return paramNodes.entrySet().stream()
        .map(e -> constructParameter(e.getValue().get(), e.getKey()))
        .toArray(Object[]::new);
  }

  private Object constructParameter(Node node, Parameter parameter) {
    Class paramType = parameter.getType();
    Function parser = PARSERS.get(paramType);
    if (parser != null && node instanceof ScalarNode) {
      return parser.apply(((ScalarNode) node).getValue());
    } else if (paramType.isEnum() && node instanceof ScalarNode) {
      return Enum.valueOf((Class) paramType, ((ScalarNode) node).getValue());
    } else {
      Object ret = constructor.constructObject(node);
      if (paramType.isAssignableFrom(ret.getClass())) {
        return ret;
      } else if (paramType.isArray() && ret instanceof List) {
        List list = (List) ret;
        Object arr = Array.newInstance(parameter.getType().getComponentType(), list.size());
        int i = 0;
        for (Object elem : list) {
          Array.set(arr, i++, elem);
        }
        return arr;
      } else if (paramType.isArray()
          && paramType.getComponentType().isAssignableFrom(ret.getClass())) {
        Object arr = Array.newInstance(parameter.getType().getComponentType(), 1);
        Array.set(arr, 0, ret);
        return arr;
      } else {
        throw new TestElementConstructorException(tag, node,
            String.format("expected a %s but got a %s", paramType.getName(),
                ret.getClass().getName()));
      }
    }
  }

  private void buildPropertiesFromList(Node props, Object ret) {
    if (props == null) {
      return;
    }
    SequenceNode propsList = castNode(props, SequenceNode.class, "list", tag);
    propsList.getValue().forEach(s -> {
      // remove first character that is the tag marker (!)
      String propertyName = s.getTag().getValue().substring(1);
      callPropertyMethod(propertyName, s, ret);
    });
  }

  private void callPropertyMethod(String propName, Node propNode, Object ret) {
    Method propMethod = findPropertyMethod(propName, propNode, ret);
    try {
      Object[] args = buildArgs(propMethod, propNode);
      propMethod.invoke(ret, args);
    } catch (ReflectiveOperationException ex) {
      throw new RuntimeException(ex);
    }
  }

  private Method findPropertyMethod(String propName, Node propNode, Object testElement) {
    List candidates = Stream.of(testElement.getClass().getMethods())
        .filter(m -> propName.equals(m.getName()))
        // sorted to get the most specific ones in the class hierarchy first
        .sorted((m1, m2) -> {
          Class m1Class = m1.getReturnType();
          Class m2Class = m2.getReturnType();
          return m1Class == m2Class ? 0 : m1Class.isAssignableFrom(m2Class) ? 1 : -1;
        })
        .collect(Collectors.toList());
    if (candidates.isEmpty()) {
      throw new TestElementConstructorException(tag, propNode,
          "could not find a method for setting property " + propName);
    }
    return candidates.stream()
        // prefer the method accepting strings which is more generic (accepts Jmeter expressions).
        .filter(m -> m.getParameters().length >= 1
            && Arrays.stream(m.getParameters())
            .allMatch(p -> String.class.isAssignableFrom(p.getType())))
        .findFirst()
        .orElseGet(() -> candidates.stream()
            /*
            if there is a method receiving just a boolean, then use it, over the one without
            parameters, to avoid improper resolution when parameter is false and method without
            parameters sets the boolean to true
             */
            .filter(m -> m.getParameters().length == 1 && boolean.class.isAssignableFrom(
                m.getParameters()[0].getType()))
            .findFirst()
            /*
             otherwise just get the first one (which is the most specific one when multiple
             implementations are available in class hierarchy)
             */
            .orElseGet(() -> candidates.get(0)));
  }

  private Object[] buildArgs(Method propMethod, Node propNode) {
    Parameter[] params = propMethod.getParameters();
    if (params.length == 0) {
      return new Object[0];
    } else if (params.length == 1) {
      return new Object[]{constructParameter(extractSingleArgNode(propNode, params[0]), params[0])};
    } else {
      Map props = getNodeProperties(propNode, propNode.getTag().getValue());
      return Arrays.stream(propMethod.getParameters())
          .map(p -> constructParameter(props.get(p.getName()), p))
          .toArray();
    }
  }

  private Node extractSingleArgNode(Node propNode, Parameter param) {
    if (propNode instanceof MappingNode) {
      MappingNode mappingNode = (MappingNode) propNode;
      if (mappingNode.getValue().size() == 1 && ((ScalarNode) mappingNode.getValue().get(0)
          .getKeyNode()).getValue().equals(param.getName())) {
        return mappingNode.getValue().get(0).getValueNode();
      }
    }
    return propNode;
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy