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

spark.route.Routes Maven / Gradle / Ivy

Go to download

A micro framework for creating web applications in Kotlin and Java 8 with minimal effort

There is a newer version: 2.9.4
Show newest version
/*
 * Copyright 2011- Per Wendel
 *
 *  Licensed 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 spark.route;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import spark.routematch.RouteMatch;
import spark.utils.MimeParse;
import spark.utils.StringUtils;

/**
 * Holds the routes and performs matching from HTTP requests to routes.
 * Works as Sinatra's, ie. if there are more than one match the one that was mapped first is chosen.
 *
 * @author Per Wendel
 */
public class Routes {

    private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(Routes.class);
    private static final char SINGLE_QUOTE = '\'';

    private List routes;

    public static Routes create() {
        return new Routes();
    }

    /**
     * Constructor
     */
    protected Routes() {
        routes = new ArrayList<>();
    }

    /**
     * Parse and validates a route and adds it
     *
     * @param route      the route path
     * @param acceptType the accept type
     * @param target     the invocation target
     */
    public void add(String route, String acceptType, Object target) {
        try {
            int singleQuoteIndex = route.indexOf(SINGLE_QUOTE);
            String httpMethod = route.substring(0, singleQuoteIndex).trim().toLowerCase(); // NOSONAR
            String url = route.substring(singleQuoteIndex + 1, route.length() - 1).trim(); // NOSONAR

            // Use special enum stuff to get from value
            HttpMethod method;
            try {
                method = HttpMethod.valueOf(httpMethod);
            } catch (IllegalArgumentException e) {
                LOG.error("The @Route value: "
                                  + route
                                  + " has an invalid HTTP method part: "
                                  + httpMethod
                                  + ".");
                return;
            }
            addRoute(method, url, acceptType, target);
        } catch (Exception e) {
            LOG.error("The @Route value: " + route + " is not in the correct format", e);
        }
    }

    /**
     * finds target for a requested route
     *
     * @param httpMethod the http method
     * @param path       the path
     * @param acceptType the accept type
     * @return the target
     */
    public RouteMatch find(HttpMethod httpMethod, String path, String acceptType) {
        List routeEntries = this.findTargetsForRequestedRoute(httpMethod, path);
        RouteEntry entry = findTargetWithGivenAcceptType(routeEntries, acceptType);
        return entry != null ? new RouteMatch(entry.target, entry.path, path, acceptType) : null;
    }

    /**
     * Finds multiple targets for a requested route.
     *
     * @param httpMethod the http method
     * @param path       the route path
     * @param acceptType the accept type
     * @return the targets
     */
    public List findMultiple(HttpMethod httpMethod, String path, String acceptType) {
        List matchSet = new ArrayList<>();
        List routeEntries = findTargetsForRequestedRoute(httpMethod, path);

        for (RouteEntry routeEntry : routeEntries) {
            if (acceptType != null) {
                String bestMatch = MimeParse.bestMatch(Arrays.asList(routeEntry.acceptedType), acceptType);

                if (routeWithGivenAcceptType(bestMatch)) {
                    matchSet.add(new RouteMatch(routeEntry.target, routeEntry.path, path, acceptType));
                }
            } else {
                matchSet.add(new RouteMatch(routeEntry.target, routeEntry.path, path, acceptType));
            }
        }

        return matchSet;
    }

    /**
     * ¨Clear all routes
     */
    public void clear() {
        routes.clear();
        RouteOverview.routes.clear();
    }

    /**
     * Removes a particular route from the collection of those that have been previously routed.
     * Search for a previously established routes using the given path and HTTP method, removing
     * any matches that are found.
     *
     * @param path       the route path
     * @param httpMethod the http method
     * @return true if this a matching route has been previously routed
     * @throws IllegalArgumentException if path is null or blank or if httpMethod is null, blank
     *                                  or an invalid HTTP method
     * @since 2.2
     */
    public boolean remove(String path, String httpMethod) {
        if (StringUtils.isEmpty(path)) {
            throw new IllegalArgumentException("path cannot be null or blank");
        }

        if (StringUtils.isEmpty(httpMethod)) {
            throw new IllegalArgumentException("httpMethod cannot be null or blank");
        }

        // Catches invalid input and throws IllegalArgumentException
        HttpMethod method = HttpMethod.valueOf(httpMethod);

        return removeRoute(method, path);
    }

    /**
     * Removes a particular route from the collection of those that have been previously routed.
     * Search for a previously established routes using the given path and removes any matches that are found.
     *
     * @param path the route path
     * @return true if this a matching route has been previously routed
     * @throws java.lang.IllegalArgumentException if path is null or blank
     * @since 2.2
     */
    public boolean remove(String path) {
        if (StringUtils.isEmpty(path)) {
            throw new IllegalArgumentException("path cannot be null or blank");
        }

        return removeRoute((HttpMethod) null, path);
    }

    //////////////////////////////////////////////////
    // PRIVATE METHODS
    //////////////////////////////////////////////////

    private void addRoute(HttpMethod method, String url, String acceptedType, Object target) {
        RouteEntry entry = new RouteEntry();
        entry.httpMethod = method;
        entry.path = url;
        entry.target = target;
        entry.acceptedType = acceptedType;
        LOG.debug("Adds route: " + entry);
        // Adds to end of list
        routes.add(entry);
        RouteOverview.add(new RouteEntry(entry), target);
    }

    //can be cached? I don't think so.
    private Map getAcceptedMimeTypes(List routes) {
        Map acceptedTypes = new HashMap<>();

        for (RouteEntry routeEntry : routes) {
            if (!acceptedTypes.containsKey(routeEntry.acceptedType)) {
                acceptedTypes.put(routeEntry.acceptedType, routeEntry);
            }
        }

        return acceptedTypes;
    }

    private boolean routeWithGivenAcceptType(String bestMatch) {
        return !MimeParse.NO_MIME_TYPE.equals(bestMatch);
    }

    private List findTargetsForRequestedRoute(HttpMethod httpMethod, String path) {
        List matchSet = new ArrayList();
        for (RouteEntry entry : routes) {
            if (entry.matches(httpMethod, path)) {
                matchSet.add(entry);
            }
        }
        return matchSet;
    }

    // TODO: I believe this feature has impacted performance. Optimization?
    private RouteEntry findTargetWithGivenAcceptType(List routeMatches, String acceptType) {
        if (acceptType != null && routeMatches.size() > 0) {
            Map acceptedMimeTypes = getAcceptedMimeTypes(routeMatches);
            String bestMatch = MimeParse.bestMatch(acceptedMimeTypes.keySet(), acceptType);

            if (routeWithGivenAcceptType(bestMatch)) {
                return acceptedMimeTypes.get(bestMatch);
            } else {
                return null;
            }
        } else {
            if (routeMatches.size() > 0) {
                return routeMatches.get(0);
            }
        }

        return null;
    }

    private boolean removeRoute(HttpMethod httpMethod, String path) {
        List forRemoval = new ArrayList<>();

        for (RouteEntry routeEntry : routes) {
            HttpMethod httpMethodToMatch = httpMethod;

            if (httpMethod == null) {
                // Use the routeEntry's HTTP method if none was given, so that only path is used to match.
                httpMethodToMatch = routeEntry.httpMethod;
            }

            if (routeEntry.matches(httpMethodToMatch, path)) {
                LOG.debug("Removing path {}", path, httpMethod == null ? "" : " with HTTP method " + httpMethod);

                forRemoval.add(routeEntry);
            }
        }

        return routes.removeAll(forRemoval);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy