package act.route; /*- * #%L * ACT Framework * %% * Copyright (C) 2014 - 2017 ActFramework * %% * 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. * #L% */ import act.Act; import org.osgl.http.H; import org.osgl.http.util.Path; import org.osgl.util.C; import org.osgl.util.E; import org.osgl.util.S; import org.osgl.util.Unsafe; import java.util.Iterator; import java.util.List; /** * {@code RouteTableRouterBuilder} take a list of route map definition line and * build the router. The line of a route map definition should look like: * <p/> * <pre> * [http-method] [url-path] [action-definition] * </pre> * <p/> * Where http-method could be one of the following: * <ul> * <li>GET</li> * <li>POST</li> * <li>PUT</li> * <li>DELETE</li> * </ul> * <p/> * url-path defines the incoming URL path, and it could * be either static or dynamic. For example, * <p/> * <pre> * # home * / * * # order list * /order * * # (dynamic) access to a certain order by ID * /order/{id} * * # (dynamic) access to a user by ID with regex spec * /user/{<[1-9]{5}>id} * </pre> * <p/> * action-definition could be in either built-in action * or controller action method. * <p/> * <p>Built-in action definition should be in a format of * <code>[directive]: [payload]</code>, for example * </p> * <p/> * <ul> * <li> * Echo - write back a text message directly * <pre> * echo: hello world! * </pre> * </li> * <li> * Redirect - send permanent redirect in the response * <pre> * redirect: http://www.google.com * </pre> * </li> * <li> * Static file directory handler - fetch files in a local directory * <pre> * staticDir: /public * </pre> * </li> * <li> * Static file locator - fetch specified file on request * <pre> * staticFile: /public/js/jquery.js * </pre> * </li> * </ul> */ public class RouteTableRouterBuilder implements RouterBuilder { public static final String ROUTES_FILE = "routes.conf"; private List<String> lines; public RouteTableRouterBuilder(List<String> lines) { E.NPE(lines); this.lines = lines; } public RouteTableRouterBuilder(String... lines) { E.illegalArgumentIf(lines.length == 0, "Empty route configuration file lines"); this.lines = C.listOf(lines); } @Override public void build(Router router) { int lineNo = lines.size(); for (int i = 0; i < lineNo; ++i) { String line = lines.get(i).trim(); if (line.startsWith("#")) continue; if (S.blank(line)) continue; try { process(line, router); } catch (RuntimeException e) { if (Act.isDev()) { router.app().setBlockIssue(e); } else { throw e; } } } } private void process(String line, Router router) { Iterator<CharSequence> itr = Path.tokenizer(Unsafe.bufOf(line), 0, ' ', '\u0000'); final String UNKNOWN = S.fmt("route configuration not recognized: %s", line); CharSequence method = null, path = null; S.Buffer action = S.newBuffer(); if (itr.hasNext()) { method = itr.next(); } else { E.illegalArgumentIf(true, UNKNOWN); } if (itr.hasNext()) { path = itr.next(); } else { E.illegalArgumentIf(true, UNKNOWN); } E.illegalArgumentIf(!itr.hasNext(), UNKNOWN); action.append(itr.next()); while (itr.hasNext()) { action.append(" ").append(itr.next()); } if ("*".contentEquals(method)) { for (H.Method m : Router.supportedHttpMethods()) { router.addMapping(m, path, action, RouteSource.ROUTE_TABLE); } } else { String s = method.toString(); if ("context".equalsIgnoreCase(s) || "ctx".equalsIgnoreCase(s)) { router.addContext(action.toString(), path.toString()); } else { H.Method m = H.Method.valueOfIgnoreCase(s); router.addMapping(m, path, action, RouteSource.ROUTE_TABLE); } } } }