/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you 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 org.elasticsearch.test.rest.yaml; import org.apache.logging.log4j.Logger; import org.elasticsearch.common.Strings; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Allows to cache the last obtained test response and or part of it within variables * that can be used as input values in following requests and assertions. */ public class Stash implements ToXContent { private static final Pattern EXTENDED_KEY = Pattern.compile("\\$\\{([^}]+)\\}"); private static final Logger logger = Loggers.getLogger(Stash.class); public static final Stash EMPTY = new Stash(); private final Map<String, Object> stash = new HashMap<>(); private final ObjectPath stashObjectPath = new ObjectPath(stash); /** * Allows to saved a specific field in the stash as key-value pair */ public void stashValue(String key, Object value) { logger.trace("stashing [{}]=[{}]", key, value); Object old = stash.put(key, value); if (old != null && old != value) { logger.trace("replaced stashed value [{}] with same key [{}]", old, key); } } /** * Clears the previously stashed values */ public void clear() { stash.clear(); } /** * Tells whether a particular key needs to be looked up in the stash based on its name. * Returns true if the string representation of the key starts with "$", false otherwise * The stash contains fields eventually extracted from previous responses that can be reused * as arguments for following requests (e.g. scroll_id) */ public boolean containsStashedValue(Object key) { if (key == null || false == key instanceof CharSequence) { return false; } String stashKey = key.toString(); if (false == Strings.hasLength(stashKey)) { return false; } if (stashKey.startsWith("$")) { return true; } return EXTENDED_KEY.matcher(stashKey).find(); } /** * Retrieves a value from the current stash. * The stash contains fields eventually extracted from previous responses that can be reused * as arguments for following requests (e.g. scroll_id) */ public Object getValue(String key) throws IOException { if (key.charAt(0) == '$' && key.charAt(1) != '{') { return unstash(key.substring(1)); } Matcher matcher = EXTENDED_KEY.matcher(key); /* * String*Buffer* because that is what the Matcher API takes. In modern versions of java the uncontended synchronization is very, * very cheap so that should not be a problem. */ StringBuffer result = new StringBuffer(key.length()); if (false == matcher.find()) { throw new IllegalArgumentException("Doesn't contain any stash keys [" + key + "]"); } do { matcher.appendReplacement(result, Matcher.quoteReplacement(unstash(matcher.group(1)).toString())); } while (matcher.find()); matcher.appendTail(result); return result.toString(); } private Object unstash(String key) throws IOException { Object stashedValue = stashObjectPath.evaluate(key); if (stashedValue == null) { throw new IllegalArgumentException("stashed value not found for key [" + key + "]"); } return stashedValue; } /** * Goes recursively against each map entry and replaces any string value starting with "$" with its * corresponding value retrieved from the stash */ public Map<String, Object> replaceStashedValues(Map<String, Object> map) throws IOException { Map<String, Object> copy = new HashMap<>(map); unstashObject(copy); return copy; } @SuppressWarnings("unchecked") private void unstashObject(Object obj) throws IOException { if (obj instanceof List) { List list = (List) obj; for (int i = 0; i < list.size(); i++) { Object o = list.get(i); if (containsStashedValue(o)) { list.set(i, getValue(o.toString())); } else { unstashObject(o); } } } if (obj instanceof Map) { Map<String, Object> map = (Map) obj; for (Map.Entry<String, Object> entry : map.entrySet()) { if (containsStashedValue(entry.getValue())) { entry.setValue(getValue(entry.getValue().toString())); } else { unstashObject(entry.getValue()); } } } } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.field("stash", stash); return builder; } }