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

no.sb1.troxy.util.Cache Maven / Gradle / Ivy

Go to download

Troxy is a proxy intended for simulating a remote service without actually connecting to the server. Incoming requests are matched using regular expressions and responses are created from a recorded response from the remote service and optionally modified by inserting values caught from the regular expression used to match the request.

There is a newer version: 3.2.49
Show newest version
package no.sb1.troxy.util;

import no.sb1.troxy.http.common.Request;
import no.sb1.troxy.record.v3.Recording;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * The Cache contains all the loaded/activated Recordings.
 * It's a tree structure (acyclic graph) which allows matching a Request to a Recording fast:
 * 
 * Root   -  Protocol  -  Host  -  Port  -  Path  -  Query  -  Method  -  Header  -  Content
 * ===================================================================================================
 * root +--- ^http$ +---- ^.*$ --- ^80$ --- ^/.*$ -- ^.*$ ---- ^GET$ ---- ^.*$ ----- ^<xml ...>$
 *      |\           \
 *      | \- ^https$  \-- ... +--- ^80$
 *       \                     \
 *        \- ...                \- ...
 * 
 */
public class Cache {
    /**
     * An enum denoting the fields in the Cache.
     */
    private enum Field {
        /**
         * This field is only used for the root of the Cache.
         */
        NONE {
            @Override
            public String getValue(Request request) {
                return "";
            }
        },
        /**
         * The protocol field of the branch.
         */
        PROTOCOL {
            @Override
            public String getValue(Request request) {
                return request.getProtocol();
            }
        },
        /**
         * The host field of the branch.
         */
        HOST {
            @Override
            public String getValue(Request request) {
                return request.getHost();
            }
        },
        /**
         * The port field of the branch.
         */
        PORT {
            @Override
            public String getValue(Request request) {
                return request.getPort();
            }
        },
        /**
         * The path field of the branch.
         */
        PATH {
            @Override
            public String getValue(Request request) {
                return request.getPath();
            }
        },
        /**
         * The query field of the branch.
         */
        QUERY {
            @Override
            public String getValue(Request request) {
                return request.getQuery();
            }
        },
        /**
         * The method field of the branch.
         */
        METHOD {
            @Override
            public String getValue(Request request) {
                return request.getMethod();
            }
        },
        /**
         * The header field of the branch.
         */
        HEADER {
            @Override
            public String getValue(Request request) {
                return request.getHeader();
            }
        },
        /**
         * The content field of the branch.
         */
        CONTENT {
            @Override
            public String getValue(Request request) {
                return request.getContent();
            }
        };

        /**
         * Get the value of this field in the given Request.
         *
         * @param request The Request to retrieve the value from.
         * @return The value of this field in Request.
         */
        public abstract String getValue(Request request);
    }

    public static class Result {
        private Recording recording;
        private Map variables;

        public Result(Recording recording, Map variables) {
            this.recording = recording;
            this.variables = variables;
        }

        public Recording getRecording() {
            return recording;
        }

        public Map getVariables() {
            return variables;
        }
    }

    /**
     * Logger for this class.
     */
    private static final Logger log = LoggerFactory.getLogger(Cache.class);
    /**
     * The root of the cache.
     */
    private final Cache root; // = new Cache(Field.NONE, null);
    /**
     * The field of this level in the cache.
     */
    private final Field field;
    /**
     * The regular expression for this field and branch.
     */
    private final Pattern pattern;
    /**
     * All the branches forking out from this branch.
     */
    private List children = new ArrayList<>();
    /**
     * At the end of a branch a single Recording may exist.
     */
    private Recording recording;

    public static Cache createCacheRoot() {
        return new Cache(null, Field.NONE, null);
    }

    /**
     * Private constructor setting field & pattern.
     *
     * @param field   The field for this branch.
     * @param pattern The pattern for this branch.
     */
    private Cache(Cache root, Field field, Pattern pattern) {
        this.root = root;
        this.field = field;
        this.pattern = pattern;
    }

    /**
     * Load Recordings into Cache.
     *
     * @param paths Files to load.
     */
    public static void loadRecordings(final Cache root, final TroxyFileHandler troxyFileHandler, final Set paths) {
        if (root.root != null) {
            throw new IllegalStateException("Trying to add records to non root cache node");
        }

        for (String path : paths) {
            if (path.endsWith(".troxy") || path.endsWith(".xml")) {
                try {
                    Recording recording = troxyFileHandler.loadRecording(path);
                    if (recording != null)
                        root.addRecoding(recording);
                } catch (Exception e) {
                    log.warn("Error reading file: {}", path, e);
                }
            } else if (!troxyFileHandler.isDirectory(path)) {
                log.info("Skipping file (unknown type): {}", path);
            }
        }
    }

    public static void loadRecordingsWithPaths(final Cache root, final TroxyFileHandler troxyFileHandler, final Set paths) {
        if (root.root != null) {
            throw new IllegalStateException("Trying to add records to non root cache node");
        }

        for (String path : paths) {
            if (path.endsWith(".troxy") || path.endsWith(".xml")) {
                try {
                    Recording recording = troxyFileHandler.loadRecording(new File(path).getParent(), new File(path).getName());
                    if (recording != null)
                        root.addRecoding(recording);
                } catch (Exception e) {
                    log.warn("Error reading file: {}", path, e);
                }
            } else if (!troxyFileHandler.isDirectory(path)) {
                log.info("Skipping file (unknown type): {}", path);
            }
        }
    }


    /**
     * Add a Recording to the Cache.
     *
     * @param recording The Recording to add.
     */
    public void addRecoding(Recording recording) {
        log.info("Adding recording to Cache: {}", recording);
        Queue queue = new ArrayDeque<>();
        queue.add(Field.PROTOCOL);
        queue.add(Field.HOST);
        queue.add(Field.PORT);
        queue.add(Field.PATH);
        queue.add(Field.QUERY);
        queue.add(Field.METHOD);
        queue.add(Field.HEADER);
        queue.add(Field.CONTENT);

        Cache root = this.root == null ? this : this.root;
        Cache current = root;
        while (!queue.isEmpty()) {
            Field f = queue.poll();
            String value = f.getValue(recording.getRequestPattern());
            boolean found = false;
            log.debug("Looking for Cache branch with field \"{}\" and pattern: {}", f, value);
            for (Cache c : current.children) {
                if (value.equals(c.pattern.pattern())) {
                    log.debug("Branch found");
                    current = c;
                    found = true;
                    break;
                }
            }
            if (!found) {
                log.debug("Branch not found, creating one");
                Cache tmp = new Cache(root, f, Pattern.compile(value, Pattern.DOTALL));
                current.children.add(tmp);
                current = tmp;
            }
        }
        if (current.recording == null) {
            /* new Recording  */
            log.debug("Recording added to cache");
        } else {
            /* overwriting existing Recording, hmm */
            log.info("Overwriting existing recording in cache");
        }
        current.recording = recording;
    }

    /**
     * Clear entire cache.
     */
    public void clear() {
        /* we'll rely on the garbage collector and just create a new root */
        log.info("Clearing Cache");

        if (root != null) {
            throw new IllegalStateException("Trying to clear records to non root cache node");
        }

        children.clear();
    }

    /**
     * Search cache for entries matching given Request.
     *
     * @param request The Request to search for.
     * @return A list of entries matching the Request.
     */
    public List searchCache(Request request) {

        if (root != null) {
            throw new IllegalStateException("Trying to search records to non root cache node");
        }

        log.info("Searching Cache for Recording matching Request: {}", request);
        return searchCache(new ArrayList<>(), new HashMap<>(), request);
    }

    /**
     * Get all the Recordings in the cache.
     *
     * @return All the Recordings in the cache.
     */
    public Set getRecordings() {

        if (root != null) {
            throw new IllegalStateException("Trying to get all records to non root cache node");
        }

        Set recordings = new HashSet<>();
        List queue = new ArrayList<>();
        queue.add(this);
        while (!queue.isEmpty()) {
            Cache current = queue.remove(0);
            queue.addAll(current.children);
            if (current.recording != null)
                recordings.add(current.recording);
        }
        return recordings;
    }

    /**
     * Helper method for searching the cache for an entry matching the Request.
     * Variables discovered while searching the cache is passed on.
     *
     * @param results   The found entries matching the Request.
     * @param variables The keys and values discovered when searching cache.
     * @param request   The Request to search for.
     * @return A list of entries matching the Request.
     */
    private List searchCache(List results, Map variables, Request request) {
        if (root != null) {
            /* need to match pattern with current field, except for root where pattern is null */
            Matcher matcher = pattern.matcher(field.getValue(request));
            if (!matcher.find()) {
                log.debug("Field {}: Pattern «{}» did not match value «{}»", field, pattern.pattern(), field.getValue(request));
                return results;
            }
            variables.put(field.toString(), matcher);
        }
        for (Cache c : children)
            c.searchCache(results, variables, request);
        if (recording != null)
            results.add(new Result(recording, variables));
        return results;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy