001/*
002 * Stallion Core: A Modern Web Framework
003 *
004 * Copyright (C) 2015 - 2016 Stallion Software LLC.
005 *
006 * This program is free software: you can redistribute it and/or modify it under the terms of the
007 * GNU General Public License as published by the Free Software Foundation, either version 2 of
008 * the License, or (at your option) any later version. This program is distributed in the hope that
009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
011 * License for more details. You should have received a copy of the GNU General Public License
012 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
013 *
014 *
015 *
016 */
017
018package io.stallion.requests;
019
020import io.stallion.Context;
021import io.stallion.exceptions.UsageException;
022import io.stallion.restfulEndpoints.RestEndpointBase;
023import io.stallion.services.Log;
024import org.apache.commons.lang3.ArrayUtils;
025
026import java.util.ArrayList;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030
031/**
032 * A registry of all routes defined in the stallion.toml settings file.
033 *
034 */
035public class RoutesRegistry {
036    private List<RouteDefinition> routes = new ArrayList<>();
037
038    private static RoutesRegistry _instance;
039
040    public static RoutesRegistry instance() {
041        if (_instance == null) {
042            throw new UsageException("You must call RoutesRegistry.load() before accessing the instance()");
043        }
044        return _instance;
045    }
046
047    public static RoutesRegistry load() {
048        _instance = new RoutesRegistry();
049        for(RouteDefinition route: Context.settings().getRoutes()) {
050            _instance.getRoutes().add(route);
051        }
052        return _instance;
053    }
054
055
056    public RouteResult routeForEndpoints(StRequest request, List<RestEndpointBase> endpoints) {
057
058        for (RestEndpointBase endpoint: endpoints) {
059            Log.finest("EndpointMethod: {0} RequestMethod: {1}", endpoint.getMethod(), request.getMethod());
060            Log.finest("EndpointRoute: {0} RequestPath: {1}", endpoint.getRoute(), request.getPath());
061
062            if (!endpoint.getMethod().equals(request.getMethod())) {
063                Log.finest("method does not match");
064                continue;
065            }
066
067
068            // Catch-all
069            if (endpoint.getRoute().equals("*")) {
070                RouteResult result = new RouteResult()
071                        .setEndpoint(endpoint);
072                return result;
073            }
074
075
076            Log.finest("Mapping pathParams");
077            String requestPath = request.getPath();
078            String[] requestParts = request.getPath().split("/");
079            String endpointPath = endpoint.getRoute();
080            String[] endpointParts = endpointPath.split("/");
081
082            // If the last part endpoint is a wildcard/match-all, then we truncate the request parts to
083            // all the path sections before the wildcard, and ignore all parts after the wildcard.
084            if (endpointParts.length > 0) {
085                if (endpointParts[endpointParts.length - 1].equals("*") && requestParts.length >= endpointParts.length) {
086                    requestParts = ArrayUtils.subarray(requestParts, 0, endpointParts.length - 1);
087                }
088            }
089
090
091            HashMap<String, String> pathParams = urlToParamsMap(requestParts, endpointParts);
092            if (pathParams != null) {
093                RouteResult result = new RouteResult()
094                        .setParams(pathParams)
095                        .setEndpoint(endpoint);
096                return result;
097            } else {
098                Log.finest("pathParams do not match");
099            }
100        }
101        return null;
102    }
103
104    public RouteResult route(String path) {
105        if ("".equals(path)) {
106            path = "/";
107        }
108        for(RouteDefinition definition: routes) {
109            HashMap<String, String> pathParams = urlToParamsMap(path.split("/"), definition.getRoute().split("/"));
110            if (pathParams != null) {
111                //val pathParamsNotNull:Map<String, String> = pathParams;
112
113                for(Map.Entry<String, String> entry: definition.getParams().entrySet()) {
114                    pathParams.put(entry.getKey(), entry.getValue());
115                }
116                RouteResult result = new RouteResult()
117                        .setTemplate(definition.getTemplate())
118                        .setGroup(definition.getGroup())
119                        .setParams(pathParams)
120                        .setPageTitle(definition.getPageTitle())
121                        .setMetaDescription(definition.getMetaDescription())
122                        .setRedirectUrl(definition.getDestination())
123                        .setPreempt(definition.getPreempt())
124                        .setName(definition.getName())
125                        ;
126                return result;
127            }
128        }
129        return null;
130    }
131
132    /**
133     *
134     * @param givenUrlSegments An array representing the URL path attempting to be opened (i.e. ["users", "42"])
135     * @param routerUrlSegments An array representing a possible URL match for the router (i.e. ["users", ":id"])
136     * @return A map of URL parameters if it's a match (i.e. {"id" =&gt; "42"}) or null if there is no match
137     */
138    public HashMap<String, String> urlToParamsMap(String[] givenUrlSegments,String[] routerUrlSegments) {
139        HashMap<String, String> formatParams = new HashMap<String, String>();
140        if (routerUrlSegments.length != givenUrlSegments.length) {
141            return null;
142        }
143        for (int index=0; index<routerUrlSegments.length; index++) {
144            String routerPart = routerUrlSegments[index];
145            if (index >= givenUrlSegments.length) {
146                return null;
147            }
148            String givenPart = givenUrlSegments[index];
149            if (routerPart.length() == 0) {
150                continue;
151            }
152            if (routerPart.charAt(0) == ':') {
153                String key = routerPart.substring(1, routerPart.length());
154                formatParams.put(key, givenPart);
155                continue;
156            }
157
158            if (!routerPart.equals(givenPart)) {
159                return null;
160            }
161        }
162        return formatParams;
163    }
164
165
166    public static void shutdown() {
167        _instance = null;
168    }
169
170
171    public List<RouteDefinition> getRoutes() {
172        return routes;
173    }
174}