com.linkedin.flashback.SceneAccessLayer.java Source code

Java tutorial

Introduction

Here is the source code for com.linkedin.flashback.SceneAccessLayer.java

Source

/*
 * Copyright (c) LinkedIn Corporation. All rights reserved. Licensed under the BSD-2 Clause license.
 * See LICENSE in the project root for license information.
 */

package com.linkedin.flashback;

import com.google.common.collect.Iterables;
import com.linkedin.flashback.matchrules.DummyMatchRule;
import com.linkedin.flashback.matchrules.MatchRule;
import com.linkedin.flashback.scene.DummyScene;
import com.linkedin.flashback.scene.Scene;
import com.linkedin.flashback.serializable.RecordedHttpExchange;
import com.linkedin.flashback.serializable.RecordedHttpRequest;
import com.linkedin.flashback.serializable.RecordedHttpResponse;
import com.linkedin.flashback.serialization.SceneWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Provide functionality to read, write and lookup RecordedHttpExchange to scene.
 * Also, it allows client to change scene at running time.
 *
 * @author shfeng
 * @author dvinegra
 */
public class SceneAccessLayer {
    static final String THE_SCENE_IS_NOT_READABLE = "the scene is not readable";
    static final String SCENE_IS_NOT_ALLOWED_BE_NULL = "scene is not allowed to be null";
    static final String MATCHRULE_IS_NOT_ALLOWED_BE_NULL = "matchrule is not allowed to be null";
    static final String SCENEWRITER_IS_NOT_ALLOWED_BE_NULL = "scenewriter is not allowed to be null";
    static final String NO_MATCHING_RECORDING_FOUND = "no matching recording found";
    static final String FAILED_TO_WRITE_SCENE_TO_THE_FILE = "Failed to write scene to the file";

    private SceneWriter _sceneWriter;
    private Scene _scene;
    private MatchRule _matchRule;
    private int _sequencePosition = 0;
    private boolean _dirty = false;

    public SceneAccessLayer(Scene scene, SceneWriter sceneWriter, MatchRule matchRule) {
        if (scene == null) {
            throw new IllegalArgumentException(SCENE_IS_NOT_ALLOWED_BE_NULL);
        }

        if (sceneWriter == null) {
            throw new IllegalArgumentException(SCENEWRITER_IS_NOT_ALLOWED_BE_NULL);
        }

        if (matchRule == null) {
            throw new IllegalArgumentException(MATCHRULE_IS_NOT_ALLOWED_BE_NULL);
        }

        _sceneWriter = sceneWriter;
        _matchRule = matchRule;
        _scene = scene;
    }

    public SceneAccessLayer(Scene scene, MatchRule matchRule) {
        this(scene, new SceneWriter(), matchRule);
    }

    public SceneAccessLayer() {
        this(new DummyScene(), new SceneWriter(), new DummyMatchRule());
    }

    public String getSceneName() {
        return _scene.getName();
    }

    /**
     * set match rule
     * */
    public void setMatchRule(MatchRule matchRule) {
        if (matchRule == null) {
            throw new IllegalArgumentException(MATCHRULE_IS_NOT_ALLOWED_BE_NULL);
        }
        _matchRule = matchRule;
    }

    /**
     * set scene if client need use switch scenes at run time.
     *
     * */
    public void setScene(Scene scene) {
        if (scene == null) {
            throw new IllegalArgumentException(SCENE_IS_NOT_ALLOWED_BE_NULL);
        }
        flush();
        _scene = scene;
        _sequencePosition = 0;
    }

    public boolean canPlayback() {
        return _scene.isReadable();
    }

    /**
     * Check if incoming http request matches any HttpExchange from the scene
     * @param request incoming request from client
     * @return true if found matched request, otherwise return false
     *
     * */
    public boolean hasMatchRequest(RecordedHttpRequest request) {
        return findMatchRequest(request) >= 0;
    }

    /**
     * Given incoming http request, find matched response from the scene and return response from the scene
     * @param request http request from client
     * @return matched http response from the scene
     *
     * */
    public RecordedHttpResponse playback(RecordedHttpRequest request) {
        if (!_scene.isReadable()) {
            throw new IllegalStateException(THE_SCENE_IS_NOT_READABLE);
        }
        int position = findMatchRequest(request);
        if (position < 0) {
            throw new IllegalStateException(NO_MATCHING_RECORDING_FOUND);
        }
        if (_scene.isSequential()) {
            _sequencePosition++;
        }
        List<RecordedHttpExchange> recordedHttpExchangeList = _scene.getRecordedHttpExchangeList();
        return recordedHttpExchangeList.get(position).getRecordedHttpResponse();
    }

    /**
     * Record request and response to the scene. Updates will be performed in-memory and will be written to disk
     * when flush() is called, or when the Scene is changed.
     * @param recordedHttpRequest http request from client
     * @param recordedHttpResponse http response from upstream service
     *
     * */
    public void record(RecordedHttpRequest recordedHttpRequest, RecordedHttpResponse recordedHttpResponse) {
        List<RecordedHttpExchange> recordedHttpExchangeList = _scene.getRecordedHttpExchangeList();
        RecordedHttpExchange recordedHttpExchange = new RecordedHttpExchange(recordedHttpRequest,
                recordedHttpResponse, new Date());
        if (!_scene.isSequential()) {
            int position = findMatchRequest(recordedHttpRequest);
            if (position >= 0) {
                recordedHttpExchangeList.set(position, recordedHttpExchange);
            } else {
                recordedHttpExchangeList.add(recordedHttpExchange);
            }
        } else {
            recordedHttpExchangeList.add(recordedHttpExchange);
        }
        _dirty = true;
    }

    /**
     * Serialize the scene to disk, if it has been updated
     */
    public void flush() {
        if (_dirty) {
            try {
                _sceneWriter.writeScene(_scene);
                _dirty = false;
            } catch (IOException e) {
                throw new RuntimeException(FAILED_TO_WRITE_SCENE_TO_THE_FILE, e);
            }
        }
    }

    /**
     * produces a string description for the match failure reason for a particular request
     * @param request incoming request that we are trying to match
     * @return a String describing the match failure reasons for the request
     */
    public String getMatchFailureDescription(RecordedHttpRequest request) {
        List<String> failureDescriptionList = new ArrayList<>();
        List<RecordedHttpExchange> exchangeList = _scene.getRecordedHttpExchangeList();
        if (_scene.isSequential()) {
            if (_sequencePosition < exchangeList.size()) {
                failureDescriptionList.add(_matchRule.getMatchFailureDescriptionForRequests(request,
                        exchangeList.get(_sequencePosition).getRecordedHttpRequest()));
            } else {
                failureDescriptionList.add("No more recorded requests in sequential scene");
            }
        } else {
            for (int i = 0; i < exchangeList.size(); i++) {
                RecordedHttpExchange exchange = exchangeList.get(i);
                failureDescriptionList.add(String.format("Recorded Request %d:%n%s", i + 1, _matchRule
                        .getMatchFailureDescriptionForRequests(request, exchange.getRecordedHttpRequest())));
            }
        }
        return new StringBuilder().append("Could not find matching request in scene " + _scene.getName() + "%n")
                .append(String.join("%n", failureDescriptionList)).toString();
    }

    /**
     * find matched request from scene
     * @param request incoming request that we'd like match in existing scene
     * @return position of list of HttpExchanges from the scene. return -1 if no match found
     *
     * */
    private int findMatchRequest(final RecordedHttpRequest request) {
        if (_scene.isSequential()) {
            List<RecordedHttpExchange> exchangeList = _scene.getRecordedHttpExchangeList();
            // In sequential playback mode, only test the request at the current sequence index
            if (_sequencePosition < exchangeList.size()
                    && _matchRule.test(request, exchangeList.get(_sequencePosition).getRecordedHttpRequest())) {
                return _sequencePosition;
            }
            return -1;
        } else {
            return Iterables.indexOf(_scene.getRecordedHttpExchangeList(),
                    input -> _matchRule.test(request, input.getRecordedHttpRequest()));
        }
    }
}