com.arextest.storage.mock.impl.DefaultMockResultProviderImpl Maven / Gradle / Ivy
package com.arextest.storage.mock.impl;
import static com.arextest.storage.model.Constants.MAX_SQL_LENGTH;
import static com.arextest.storage.model.Constants.MAX_SQL_LENGTH_DEFAULT;
import com.arextest.common.cache.CacheProvider;
import com.arextest.common.utils.CompressionUtils;
import com.arextest.config.model.vo.QueryConfigOfCategoryResponse.QueryConfigOfCategory;
import com.arextest.model.mock.AREXMocker;
import com.arextest.model.mock.MockCategoryType;
import com.arextest.model.mock.Mocker;
import com.arextest.storage.cache.CacheKeyUtils;
import com.arextest.storage.metric.MatchStrategyMetricService;
import com.arextest.storage.mock.EigenProcessor;
import com.arextest.storage.mock.MatchKeyFactory;
import com.arextest.storage.mock.MockResultContext;
import com.arextest.storage.mock.MockResultMatchStrategy;
import com.arextest.storage.mock.MockResultProvider;
import com.arextest.storage.mock.MockerResultConverter;
import com.arextest.storage.mock.internal.matchkey.impl.DubboConsumerMatchKeyBuilderImpl;
import com.arextest.storage.model.ByteHashKey;
import com.arextest.storage.model.MockResultType;
import com.arextest.storage.serialization.ZstdJacksonSerializer;
import com.arextest.storage.service.QueryConfigService;
import com.arextest.storage.service.DatabaseParseService;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Resource;
import javax.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
@Slf4j
final class DefaultMockResultProviderImpl implements MockResultProvider {
private static final int EMPTY_SIZE = 0;
private static final String CALL_REPLAY_MAX = "callReplayMax";
private static final String DUBBO_PREFIX = "Dubbo";
private static final String STRICT_MATCH = "strictMatch";
private static final String MULTI_OPERATION_WITH_STRICT_MATCH = "multiOperationStrictMatch";
private static final String FUZZY_MATCH = "fuzzyMatch";
private static final String EIGEN_MATCH = "eigenMatch";
private static final String COMMA_STRING = ",";
/**
* default 2h expired
*/
@Value("${arex.storage.cache.expired.seconds:7200}")
private long cacheExpiredSeconds;
@Value("${arex.storage.query.config:true}")
private boolean queryConfigSwitch;
@Resource
private CacheProvider redisCacheProvider;
@Resource
private ZstdJacksonSerializer serializer;
@Resource
private MatchKeyFactory matchKeyFactory;
@Resource
private MatchStrategyMetricService matchStrategyMetricService;
@Resource
private DubboConsumerMatchKeyBuilderImpl dubboConsumerMatchKeyBuilder;
@Resource
private QueryConfigService queryConfigService;
@Resource
private MockerResultConverter mockerResultConverter;
@Resource
private DatabaseParseService databaseParseService;
/**
* 1. Store recorded data and matching keys in redis<>
* 2. The mock type associated with dubbo,which needs to record the maximum number of replays.
* 3. renewal cache clearer
*/
@Override
public boolean putRecordResult(MockCategoryType category, String recordId,
Iterable values) {
boolean shouldRecordCallReplayMax = shouldRecordCallReplayMax(category);
// key: Redis keys that need to be counted. value: The number of redis keys
Map mockSequenceKeyMaps = Maps.newHashMap();
Iterator valueIterator = values.iterator();
// Records the maximum number of operations corresponding to recorded data
List mockList = new ArrayList<>();
// Obtain the number of the same interfaces in recorded data
while (valueIterator.hasNext()) {
T value = valueIterator.next();
databaseParseService.regenerateOperationName(value);
T convertedMocker = mockerResultConverter.convert(category, value);
calcCallReplayMax(shouldRecordCallReplayMax, category, recordId, convertedMocker,
mockSequenceKeyMaps);
mockList.add(convertedMocker);
}
mockList.sort(Comparator.comparing(Mocker::getCreationTime));
HashMap callReplayMaxMap = Maps.newHashMap(mockSequenceKeyMaps);
final byte[] recordIdBytes = CacheKeyUtils.toUtf8Bytes(recordId);
byte[] recordKey = CacheKeyUtils.buildRecordKey(category, recordIdBytes);
int size = 0;
int mockListSize = mockList.size();
for (int sequence = 1; sequence <= mockListSize; sequence++) {
T value = mockList.get(sequence - 1);
addCallReplayMax(shouldRecordCallReplayMax, category, recordId, value, callReplayMaxMap);
size = sequencePutRecordData(category, recordIdBytes, size, recordKey, value, sequence,
mockSequenceKeyMaps);
}
LOGGER.info("update record cache, count: {}, recordId: {}, category: {}", mockListSize,
recordId, category);
putRedisValue(recordKey, mockListSize);
LOGGER.info("put record result to cache size:{} for category:{},record id:{}", size, category,
recordId);
return size > EMPTY_SIZE;
}
// Place the maximum number of playback times corresponding to the operations into the recorded data
private void calcCallReplayMax(boolean shouldRecordCallReplayMax, MockCategoryType category,
String recordId, Mocker value,
Map mockSequenceKeyMaps) {
if (!shouldRecordCallReplayMax) {
return;
}
byte[] recordOperationKey = CacheKeyUtils.buildRecordOperationKey(category, recordId,
getOperationNameWithCategory(value));
int count = updateMapsAndGetCount(mockSequenceKeyMaps, recordOperationKey);
LOGGER.info("update record operation cache, count: {}, operation: {}", count,
value.getOperationName());
}
private void addCallReplayMax(boolean shouldRecordCallReplayMax, MockCategoryType category,
String recordId, Mocker value, Map mockSequenceKeyMaps) {
if (!shouldRecordCallReplayMax) {
return;
}
byte[] recordOperationKey = CacheKeyUtils.buildRecordOperationKey(category, recordId,
getOperationNameWithCategory(value));
int count = mockSequenceKeyMaps.getOrDefault(new ByteHashKey(recordOperationKey), 0);
Mocker.Target targetResponse = value.getTargetResponse();
if (targetResponse != null) {
targetResponse.setAttribute(CALL_REPLAY_MAX, count);
}
}
private void putRedisValue(byte[] recordOperationKey, int count) {
redisCacheProvider.put(recordOperationKey, cacheExpiredSeconds, CacheKeyUtils.toUtf8Bytes(
String.valueOf(count)));
}
private int sequencePutRecordData(MockCategoryType category,
byte[] recordIdBytes, int size, byte[] recordKey, T value, int sequence,
Map mockSequenceKeyMaps) {
if (MapUtils.isEmpty(value.getEigenMap())) {
calculateEigen(value, true);
}
List mockKeyList = matchKeyFactory.build(value);
final byte[] zstdValue = serializer.serialize(value);
byte[] valueRefKey = sequencePut(recordKey, zstdValue, sequence);
LOGGER.info("update record sequence cache, count: {}", sequence);
if (valueRefKey == null) {
return size;
}
for (int i = 0; i < mockKeyList.size(); i++) {
byte[] mockKeyBytes = mockKeyList.get(i);
byte[] key = CacheKeyUtils.buildRecordKey(category, recordIdBytes, mockKeyBytes);
int count = updateMapsAndGetCount(mockSequenceKeyMaps, key);
LOGGER.info("update record mock key cache, count: {}, mock index: {}, operation: {}",
count, i, value.getOperationName());
putRedisValue(key, count);
byte[] sequenceKey = sequencePut(key, valueRefKey, count);
if (sequenceKey != null) {
size++;
}
}
// if category type is the type to be compared.associate the mock instance id with the related mock key.
if (shouldUseIdOfInstanceToMockResult(category)) {
putRecordInstanceId(valueRefKey, value.getId());
putMockKeyListWithInstanceId(value.getId(), mockKeyList);
}
return size;
}
/**
* Obtain the corresponding value in the maps through the key and update it. if key exist in
* maps,increase the value by 1
*/
private int updateMapsAndGetCount(Map maps, byte[] key) {
return maps.merge(new ByteHashKey(key), 1, Integer::sum);
}
private boolean shouldUseIdOfInstanceToMockResult(MockCategoryType category) {
return !category.isEntryPoint();
}
private boolean shouldRecordCallReplayMax(MockCategoryType category) {
return shouldUseIdOfInstanceToMockResult(category) && (category.getName()
.startsWith(DUBBO_PREFIX) || category.equals(MockCategoryType.DYNAMIC_CLASS)
|| category.equals(MockCategoryType.REDIS));
}
@Override
public boolean removeRecordResult(MockCategoryType category, String recordId,
Iterable values) {
int removed = EMPTY_SIZE;
final byte[] recordIdBytes = CacheKeyUtils.toUtf8Bytes(recordId);
byte[] recordCountKey = CacheKeyUtils.buildRecordKey(category, recordId);
Iterator valueIterator = values.iterator();
while (valueIterator.hasNext()) {
T value = valueIterator.next();
removed += removeResult(category, recordIdBytes, value);
}
int count = resultCount(recordCountKey);
if (count > EMPTY_SIZE) {
redisCacheProvider.remove(recordCountKey);
for (int sequence = 1; sequence <= count; sequence++) {
final byte[] resultSequenceKey = createSequenceKey(recordCountKey, sequence);
if (redisCacheProvider.remove(resultSequenceKey)) {
removed++;
}
}
}
LOGGER.info("remove record result size:{} for category:{},record id:{}", removed, category,
recordId);
return removed > EMPTY_SIZE;
}
@Override
public void calculateEigen(Mocker item, boolean queryConfig) {
try {
if (item.getCategoryType().isEntryPoint()) {
return;
}
String eigenBody = matchKeyFactory.getEigenBody(item);
if (StringUtils.isEmpty(eigenBody)) {
return;
}
// get exclusion and ignore node from arex-api.use this to reduction noise
Collection> exclusions = null;
Collection ignoreNodes = null;
if (queryConfig && queryConfigSwitch) {
QueryConfigOfCategory queryConfigOfCategory = queryConfigService.queryConfigOfCategory(
item);
if (queryConfigOfCategory != null) {
exclusions = queryConfigOfCategory.getExclusionList();
ignoreNodes = queryConfigOfCategory.getIgnoreNodeSet();
}
}
Map calculateEigen = EigenProcessor.calculateEigen(eigenBody,
item.getCategoryType().getName(), exclusions, ignoreNodes);
if (MapUtils.isEmpty(calculateEigen)) {
return;
}
item.setEigenMap(calculateEigen);
} catch (Exception e) {
LOGGER.error("setCalculateEigen failed!", e);
}
}
private int removeResult(MockCategoryType category,
byte[] recordIdBytes, T value) {
int removed = EMPTY_SIZE;
List mockKeyList = matchKeyFactory.build(value);
for (int i = 0; i < mockKeyList.size(); i++) {
byte[] mockKeyBytes = mockKeyList.get(i);
byte[] key = CacheKeyUtils.buildRecordKey(category, recordIdBytes, mockKeyBytes);
int resultCount = resultCount(key);
if (resultCount <= EMPTY_SIZE) {
continue;
}
for (int sequence = 1; sequence <= resultCount; sequence++) {
byte[] sequenceKey = createSequenceKey(key, sequence);
if (redisCacheProvider.remove(sequenceKey)) {
removed++;
}
}
if (redisCacheProvider.remove(key)) {
removed++;
}
}
return removed;
}
@Override
public boolean putReplayResult(T value) {
MockCategoryType category = value.getCategoryType();
String replayResultId = value.getReplayId();
byte[] zstdValue = serializer.serialize(value);
final byte[] key = CacheKeyUtils.buildReplayKey(category, replayResultId);
boolean success = sequencePut(key, zstdValue) != null;
LOGGER.info("put replay result:{} for category:{},result id:{}", success, category,
replayResultId);
return success;
}
private int nextSequence(byte[] key) {
long count = redisCacheProvider.incrValue(key);
redisCacheProvider.expire(key, cacheExpiredSeconds);
return (int) count;
}
private byte[] sequencePut(final byte[] key, final byte[] zstdValue) {
int next = 0;
try {
next = nextSequence(key);
final byte[] sequenceKey = createSequenceKey(key, next);
boolean retResult = redisCacheProvider.put(sequenceKey, cacheExpiredSeconds, zstdValue);
if (retResult) {
return sequenceKey;
}
} catch (Throwable throwable) {
LOGGER.error("redis put error:{} sequence:{} for base64 key:{}",
throwable.getMessage(), next, CompressionUtils.encodeToBase64String(key), throwable);
}
return null;
}
private byte[] sequencePut(final byte[] key, final byte[] zstdValue, int sequence) {
try {
final byte[] sequenceKey = createSequenceKey(key, sequence);
boolean retResult = redisCacheProvider.put(sequenceKey, cacheExpiredSeconds, zstdValue);
if (retResult) {
return sequenceKey;
}
} catch (Throwable throwable) {
LOGGER.error("redis put error::{} sequence:{} for base64 key:{}",
throwable.getMessage(), sequence, CompressionUtils.encodeToBase64String(key), throwable);
}
return null;
}
/**
* sequence query for record result,if consume overhead the total,we use last one instead as
* return.
*
* if the accurate matches the data, let the fuzzy key related to the mock data increase. vice
* versa.
*
* @param mockItem The record id from agent produced
* @return compressed bytes with zstd
*/
@Override
public byte[] getRecordResult(@NotNull Mocker mockItem, MockResultContext context) {
MockCategoryType category = mockItem.getCategoryType();
String recordId = mockItem.getRecordId();
String replayId = mockItem.getReplayId();
try {
long start = System.currentTimeMillis();
calculateEigen(mockItem, false);
List mockKeyList = matchKeyFactory.build(mockItem);
long end = System.currentTimeMillis();
LOGGER.info("build mock keys cost:{} ms", end - start);
if (CollectionUtils.isEmpty(mockKeyList)) {
LOGGER.warn("build empty mock keys,skip mock result query,recordId:{},replayId:{}",
recordId, replayId);
return null;
}
final byte[] recordIdBytes = CacheKeyUtils.toUtf8Bytes(recordId);
final byte[] replayIdBytes = CacheKeyUtils.toUtf8Bytes(replayId);
byte[] fuzzMockKeyBytes = mockKeyList.get(mockKeyList.size() - 1);
byte[] fuzzMockSourceKey = CacheKeyUtils.buildRecordKey(category, recordIdBytes,
fuzzMockKeyBytes);
int count = resultCount(fuzzMockSourceKey);
LOGGER.info("get record result with operation:{}, count: {}",
CacheKeyUtils.fromUtf8Bytes(fuzzMockKeyBytes), count);
if (useSequenceMatch(context.getMockStrategy(), category, count)) {
return getMockResultWithSequenceMatch(mockItem, context, category, mockKeyList,
recordIdBytes, replayIdBytes);
}
return getMockResultWithEigenMatch(category, recordIdBytes, replayIdBytes,
mockKeyList, mockItem, count, context);
} catch (Throwable throwable) {
LOGGER.error(
"from agent's sequence consumeResult error: {} for category: {}, recordId: {}, replayId: {}",
throwable.getMessage(), category, recordId, replayId, throwable);
}
return null;
}
private boolean useSequenceMatch(MockResultMatchStrategy mockStrategy, MockCategoryType category,
int recordDataCount) {
return category.isSkipComparison()
|| mockStrategy == MockResultMatchStrategy.STRICT_MATCH
|| recordDataCount <= 1;
}
/**
* Get the method name corresponding to the type. For dubbo, it is necessary to obtain the exact
* matching key as the method name to calculate the quantity.
*
* @param mockItem
* @return
*/
private byte[] getOperationNameWithCategory(Mocker mockItem) {
String operationName = mockItem.getOperationName();
byte[] operationKey = CacheKeyUtils.toUtf8Bytes(operationName);
if (!mockItem.getCategoryType().isEntryPoint() &&
mockItem.getCategoryType().getName().startsWith(DUBBO_PREFIX)) {
List build = dubboConsumerMatchKeyBuilder.build(mockItem);
if (CollectionUtils.isEmpty(build)) {
return operationKey;
}
return build.get(0);
}
return operationKey;
}
private byte[] getMockResultWithSequenceMatch(Mocker mockItem, MockResultContext context,
MockCategoryType category, List mockKeyList, byte[] recordIdBytes,
byte[] replayIdBytes) {
byte[] result = null;
byte[] mockResultId = null;
byte[] mockKeyBytes;
int mockResultIndex = 0;
int mockKeySize = mockKeyList.size();
boolean useInstanceIdToMockResult = shouldUseIdOfInstanceToMockResult(category);
boolean strictMatch = context.getMockStrategy() == MockResultMatchStrategy.STRICT_MATCH;
for (int i = 0; i < mockKeySize; i++) {
mockKeyBytes = mockKeyList.get(i);
result = sequenceMockResult(category, recordIdBytes, replayIdBytes, mockKeyBytes, context);
// if mock result match strategy is strict match, need get full parameter match data.
if (strictMatch) {
return result;
}
if (result != null) {
if (useInstanceIdToMockResult) {
// associate the matched mock id with the related replay data.
mockResultId = getIdOfRecordInstance(context.getValueRefKey());
mockItem.setId(CacheKeyUtils.fromUtf8Bytes(mockResultId));
LOGGER.info(
"get record result from record instance id: {}, operation: {}, strict match: {}, match key index: {}",
CacheKeyUtils.fromUtf8Bytes(mockResultId), mockItem.getOperationName(), i == 0, i);
}
matchStrategyMetricService.recordMatchingCount(i == 0 ? STRICT_MATCH : FUZZY_MATCH,
(AREXMocker) mockItem);
mockResultIndex = i;
break;
}
}
if (result != null && useInstanceIdToMockResult) {
updateConsumeSequence(category, recordIdBytes, replayIdBytes, mockResultId, mockResultIndex,
mockKeySize);
}
return result;
}
/**
* Treat both full parameter and fuzzy matching of recorded data as a set of data, with one key
* being consumed and the other keys also consumed
*
* follows: 1. if the fuzzy match gets the data, just let other key match increase. 2. if the full
* parameter match gets the data, only needs to increase the fuzzy match.
*/
private void updateConsumeSequence(MockCategoryType category, byte[] recordIdBytes,
byte[] replayIdBytes,
byte[] mockResultId, int mockResultIndex, int mockKeySize) {
for (int i = 0; i < mockKeySize; i++) {
if (mockResultIndex == i) {
continue;
}
byte[] mockKeyWithInstanceId = getMockKeyListWithInstanceId(mockResultId, i);
byte[] consumeSource = CacheKeyUtils.buildConsumeKey(category, recordIdBytes, replayIdBytes,
mockKeyWithInstanceId);
nextSequence(consumeSource);
}
}
private int coincidePath(Map replayEigenMap, Map recordEigenMap) {
int row = 0;
if (MapUtils.isEmpty(replayEigenMap) || MapUtils.isEmpty(recordEigenMap)) {
return row;
}
for (Map.Entry entry : recordEigenMap.entrySet()) {
Integer key = entry.getKey();
Long recordPathValue = recordEigenMap.get(key);
Long replayPathValue = replayEigenMap.get(key);
if (Objects.equals(recordPathValue, replayPathValue)) {
row++;
}
}
return row;
}
private byte[] sequenceMockResult(MockCategoryType category, final byte[] recordIdBytes,
byte[] replayIdBytes,
final byte[] mockKeyBytes, MockResultContext context) {
try {
byte[] sourceKey = CacheKeyUtils.buildRecordKey(category, recordIdBytes, mockKeyBytes);
int count = resultCount(sourceKey);
if (count == EMPTY_SIZE) {
return null;
}
byte[] consumeSource = CacheKeyUtils.buildConsumeKey(category, recordIdBytes, replayIdBytes,
mockKeyBytes);
int sequence = nextSequence(consumeSource);
boolean tryFindLastValue =
MockResultMatchStrategy.TRY_FIND_LAST_VALUE == context.getMockStrategy();
context.setLastOfResult(sequence > count);
if (context.isLastOfResult() && tryFindLastValue) {
LOGGER.info(
"overhead consume record result,try use last one instead it,current sequence:{},count:{}"
, sequence, count);
sequence = count;
}
byte[] consumeSequenceKey = createSequenceKey(sourceKey, sequence);
byte[] valueRefKey = redisCacheProvider.get(consumeSequenceKey);
if (valueRefKey != null) {
context.setValueRefKey(valueRefKey);
return redisCacheProvider.get(valueRefKey);
}
} catch (Throwable throwable) {
LOGGER.error("from agent's sequence consumeResult error:{} for category:{}",
throwable.getMessage(),
category, throwable);
}
return null;
}
private int getReplayConsumerCount(MockCategoryType category, byte[] recordIdBytes,
byte[] replayIdBytes,
String recordInstanceId) {
byte[] usedRecordInstanceIdsKey = buildMatchedRecordInstanceIdsKey(category, recordIdBytes,
replayIdBytes, CacheKeyUtils.toUtf8Bytes(recordInstanceId));
return resultCount(usedRecordInstanceIdsKey);
}
/**
* the matching result is obtained by mocker eigen. 1.If it can be accurately matched, match it
* first 2.Those who cannot be accurately matched will find all candidates 2.1 Find the eigen
* values of all candidates and calculate the number of nodes that overlap with the feature values
* of the playback data 2.2 Find the value with the highest number of overlapping nodes and return
* it as matching data
*/
public byte[] getMockResultWithEigenMatch(MockCategoryType category,
final byte[] recordIdBytes, byte[] replayIdBytes, List mockKeyList,
@NotNull Mocker mockItem,
int count, MockResultContext context) {
try {
// 1. determine whether it can be accurately matched in multiple call scenarios
byte[] result = sequenceMockResult(category, recordIdBytes, replayIdBytes, mockKeyList.get(0),
context);
// 2. the data on the exact match is returned directly
if (result != null) {
byte[] mockResultId = getIdOfRecordInstance(context.getValueRefKey());
String id = CacheKeyUtils.fromUtf8Bytes(mockResultId);
mockItem.setId(id);
long increasesCount = increasesReplayConsumer(category, recordIdBytes, replayIdBytes,
mockResultId);
if (increasesCount <= 1L) {
matchStrategyMetricService.recordMatchingCount(MULTI_OPERATION_WITH_STRICT_MATCH,
(AREXMocker) mockItem);
LOGGER.info(
"[[title=eigenMatch]]get mock result with eigen match, instanceId: {}, increasesCount: {}",
id, increasesCount);
return result;
}
}
// 3. use eigen to match
byte[] fuzzMockKeyBytes = mockKeyList.get(mockKeyList.size() - 1);
byte[] sourceKey = CacheKeyUtils.buildRecordKey(category, recordIdBytes, fuzzMockKeyBytes);
String operationName = mockItem.getOperationName();
boolean tryFindLastValue =
context.getMockStrategy() == MockResultMatchStrategy.TRY_FIND_LAST_VALUE;
LOGGER.info(
"[[title=eigenMatch]]get mock result with eigen match, recordDataCount: {}", count);
// 3.1 iterate over all records, calculating the eigen between replay requests and record requests.
// invocationMap: Map>>
Map>> invocationMap = Maps.newHashMap();
for (int sequence = 1; sequence <= count; sequence++) {
byte[] mockDataBytes = getMockerDataBytesFromMockKey(sourceKey, sequence);
if (mockDataBytes == null) {
continue;
}
AREXMocker mocker = serializer.deserialize(mockDataBytes, AREXMocker.class);
String recordInstanceId = mocker.getId();
int consumerCount = getReplayConsumerCount(category, recordIdBytes, replayIdBytes,
recordInstanceId);
if (consumerCount > EMPTY_SIZE) {
if (tryFindLastValue && sequence == count) {
LOGGER.info(
"[[title=eigenMatch]]try find last value, recordInstanceId: {}, consumerCount: {}",
recordInstanceId, consumerCount);
return mockDataBytes;
}
LOGGER.info(
"[[title=eigenMatch]]operation: {}, recordInstanceId: {} is matched",
operationName, recordInstanceId);
continue;
}
addToInvocationMap(mockItem, mocker, recordInstanceId, mockDataBytes, invocationMap);
}
if (MapUtils.isEmpty(invocationMap)) {
return null;
}
// 3.2 sort the matching results by eigen map.
List scores = getScores(invocationMap);
for (Integer score : scores) {
List> pairList = invocationMap.get(score);
for (Pair pair : pairList) {
// 3.3 put the matched recording id into the cache.
String instanceId = pair.getLeft();
if (StringUtils.isEmpty(instanceId)
|| increasesReplayConsumer(category, recordIdBytes, replayIdBytes,
CacheKeyUtils.toUtf8Bytes(instanceId)) > 1L) {
LOGGER.info("[[title=eigenMatch]]operation: {}, recordInstanceId: {} is matched.",
operationName, instanceId);
continue;
}
mockItem.setId(instanceId);
LOGGER.info(
"[[title=eigenMatch]]get mock result with eigen match, operation: {}, score: {}, matchedInstanceId: {}",
operationName, score, instanceId);
// 3.4. buried point record the number of times similarity is used.
matchStrategyMetricService.recordMatchingCount(EIGEN_MATCH, (AREXMocker) mockItem);
return pair.getRight();
}
}
return null;
} catch (Throwable throwable) {
LOGGER.error(
"[[title=eigenMatch]]getMockResultWithEigenMatch error: {}, category:{}",
throwable.getMessage(), category, throwable);
}
return null;
}
/**
* Sort by similarity and overlap
*/
private static List getScores(Map>> invocationMap) {
List scores = new ArrayList<>(invocationMap.keySet());
scores.sort(Collections.reverseOrder());
return scores;
}
/**
* Put the similarity value and the corresponding mock information in the map
*/
private void addToInvocationMap(Mocker mockItem, AREXMocker mocker, String recordInstanceId,
byte[] mockDataBytes, Map>> invocationMap) {
Map recordEigenMap = mocker.getEigenMap();
int coincidePath = coincidePath(mockItem.getEigenMap(), recordEigenMap);
LOGGER.info("[[title=eigenMatch]]recordInstanceId: {}, paths: {}", recordInstanceId,
coincidePath);
Pair pair = ImmutablePair.of(recordInstanceId, mockDataBytes);
if (MapUtils.isEmpty(invocationMap) || invocationMap.get(coincidePath) == null) {
List> pairs = Lists.newArrayListWithExpectedSize(1);
pairs.add(pair);
invocationMap.put(coincidePath, pairs);
} else {
List> pairs = invocationMap.get(coincidePath);
pairs.add(pair);
invocationMap.put(coincidePath, pairs);
}
}
private byte[] getMockerDataBytesFromMockKey(byte[] sourceKey, int sequence) {
byte[] consumeSequenceKey = createSequenceKey(sourceKey, sequence);
byte[] valueRefKey = redisCacheProvider.get(consumeSequenceKey);
if (valueRefKey == null) {
return null;
}
return redisCacheProvider.get(valueRefKey);
}
private long increasesReplayConsumer(MockCategoryType category, byte[] recordIdBytes,
byte[] replayIdBytes, byte[] mockResultId) {
byte[] usedRecordInstanceIdsKey = buildMatchedRecordInstanceIdsKey(category, recordIdBytes,
replayIdBytes,
mockResultId);
return nextSequence(usedRecordInstanceIdsKey);
}
private byte[] buildMatchedRecordInstanceIdsKey(MockCategoryType category, byte[] recordIdBytes,
byte[] replayIdBytes,
byte[] mockResultId) {
return CacheKeyUtils.buildMatchedRecordInstanceIdsKey(category,
recordIdBytes, replayIdBytes, mockResultId);
}
@Override
public List getRecordResultList(MockCategoryType category, String recordId) {
byte[] recordCountKey = CacheKeyUtils.buildRecordKey(category, recordId);
return getResultList(recordCountKey);
}
private List getResultList(byte[] resultCountKey) {
int size = resultCount(resultCountKey);
if (size == EMPTY_SIZE) {
return Collections.emptyList();
}
final List recordResult = new ArrayList<>(size);
for (int sequence = 1; sequence <= size; sequence++) {
byte[] sequenceKey = createSequenceKey(resultCountKey, sequence);
byte[] value = redisCacheProvider.get(sequenceKey);
if (value != null) {
recordResult.add(value);
}
}
return recordResult;
}
@Override
public List getReplayResultList(MockCategoryType category, String replayResultId) {
byte[] replayKey = CacheKeyUtils.buildReplayKey(category, replayResultId);
return getResultList(replayKey);
}
@Override
public int replayResultCount(MockCategoryType category, String replayResultId) {
return resultCount(CacheKeyUtils.buildReplayKey(category, replayResultId));
}
@Override
public int recordResultCount(MockCategoryType category, String recordId) {
return resultCount(CacheKeyUtils.buildRecordKey(category, recordId));
}
private int resultCount(byte[] countKey) {
byte[] totalBytes = redisCacheProvider.get(countKey);
if (totalBytes == null) {
return EMPTY_SIZE;
}
String totalText = new String(totalBytes);
return StringUtils.isEmpty(totalText) ? EMPTY_SIZE : Integer.parseInt(totalText);
}
private byte[] createSequenceKey(byte[] src, int sequence) {
return CacheKeyUtils.merge(src, sequence);
}
private void putRecordInstanceId(byte[] valueRefKey, String id) {
final byte[] recordInstanceIdKey = createRecordInstanceIdKey(valueRefKey);
redisCacheProvider.put(recordInstanceIdKey, cacheExpiredSeconds, CacheKeyUtils.toUtf8Bytes(id));
}
/**
* associate the mock instance id with the related mock key.
*/
private void putMockKeyListWithInstanceId(String id, List mockKeyList) {
for (int i = 0; i < mockKeyList.size(); i++) {
byte[] mockKeyWithInstanceIdKey = createMockKeyWithInstanceIdKey(
CacheKeyUtils.toUtf8Bytes(id), i);
redisCacheProvider.put(mockKeyWithInstanceIdKey, cacheExpiredSeconds, mockKeyList.get(i));
}
}
private byte[] getMockKeyListWithInstanceId(byte[] mockResultId, int index) {
byte[] mockKeyWithInstanceIdKey = createMockKeyWithInstanceIdKey(mockResultId, index);
return redisCacheProvider.get(mockKeyWithInstanceIdKey);
}
private byte[] getIdOfRecordInstance(byte[] valueRefKey) {
final byte[] recordInstanceIdKey = createRecordInstanceIdKey(valueRefKey);
return redisCacheProvider.get(recordInstanceIdKey);
}
private byte[] createRecordInstanceIdKey(byte[] src) {
return CacheKeyUtils.merge(src, MockResultType.RECORD_INSTANCE_ID.getCodeValue());
}
private byte[] createMockKeyWithInstanceIdKey(byte[] src, int index) {
return CacheKeyUtils.merge(src, index);
}
}