/** * Copyright (c) 2015, biezhi 王爵 (biezhi.me@gmail.com) * <p> * 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 * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * 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 com.blade.mvc.route.loader; import com.blade.exception.RouteException; import com.blade.kit.IOKit; import com.blade.mvc.http.HttpMethod; import com.blade.mvc.http.Request; import com.blade.mvc.http.Response; import com.blade.mvc.route.Route; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Method; import java.text.ParseException; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; /** * Abstract loader implementation * * @author <a href="mailto:biezhi.me@gmail.com" target="_blank">biezhi</a> * @since 1.5 */ public abstract class AbstractFileRouteLoader implements RouteLoader { private ControllerLoader controllerLoader = new ClassPathControllerLoader(); protected abstract InputStream getInputStream() throws Exception; @Override public List<Route> load() throws ParseException, RouteException { InputStream inputStream = null; try { inputStream = getInputStream(); } catch (Exception e) { throw new RouteException("Loading the route config file error: " + e.getMessage(), e); } try { return load(inputStream); } catch (IOException e) { throw new RouteException("Loading the route config file error: " + e.getMessage(), e); } } /** * Load Route * * @param inputStream route inputstream * @return return route list * @throws ParseException parse exception * @throws IOException io exception */ private List<Route> load(InputStream inputStream) throws ParseException, IOException { int line = 0; List<Route> routes = new ArrayList<Route>(); BufferedReader in = null; try { in = new BufferedReader(new InputStreamReader(inputStream)); String input; while ((input = in.readLine()) != null) { line++; input = input.trim(); if (!input.equals("") && !input.startsWith(".")) { Route route = parse(input, line); routes.add(route); } } } finally { IOKit.closeQuietly(in); } return routes; } private Route parse(String input, int line) throws ParseException { StringTokenizer st = new StringTokenizer(input, " \t"); if (st.countTokens() != 3) { throw new ParseException("Unrecognized format", line); } // Verify HTTP request String httpMethod = validateHttpMethod(st.nextToken().trim(), line); String path = validatePath(st.nextToken().trim(), line); String controllerAndMethod = validateControllerAndMethod(st.nextToken().trim(), line); int hashPos = controllerAndMethod.indexOf("."); String controllerName = controllerAndMethod.substring(0, hashPos); // Acquisition controller method String controllerMethod = controllerAndMethod.substring(hashPos + 1); return buildRoute(httpMethod, path, controllerName, controllerMethod); } private String validateHttpMethod(String httpMethod, int line) throws ParseException { if (!httpMethod.equalsIgnoreCase("GET") && !httpMethod.equalsIgnoreCase("POST") && !httpMethod.equalsIgnoreCase("PUT") && !httpMethod.equalsIgnoreCase("DELETE")) { throw new ParseException("Unrecognized HTTP method: " + httpMethod, line); } return httpMethod; } private String validatePath(String path, int line) throws ParseException { if (!path.startsWith("/")) { throw new ParseException("Path must start with '/'", line); } boolean openedKey = false; for (int i = 0; i < path.length(); i++) { boolean validChar = isValidCharForPath(path.charAt(i), openedKey); if (!validChar) { throw new ParseException(path, i); } if (path.charAt(i) == '{') { openedKey = true; } if (path.charAt(i) == '}') { openedKey = false; } } return path; } private boolean isValidCharForPath(char c, boolean openedKey) { char[] invalidChars = {'?', '.', ' '}; for (char invalidChar : invalidChars) { if (c == invalidChar) { return false; } } if (openedKey) { char[] moreInvalidChars = {'/', '{'}; for (char invalidChar : moreInvalidChars) { if (c == invalidChar) { return false; } } } return true; } /** * Verification controller method * * @param beanAndMethod controller and method, using. * @param line line number * @return return a string that is verified after the verification. * @throws ParseException */ private String validateControllerAndMethod(String beanAndMethod, int line) throws ParseException { int hashPos = beanAndMethod.indexOf("."); if (hashPos == -1) { throw new ParseException("Unrecognized format for '" + beanAndMethod + "'", line); } return beanAndMethod; } /** * Construct a routing object * * @param httpMethod request httpMethod * @param path route path * @param controllerName controller name * @param methodName method name * @return return route object * @throws RouteException */ private Route buildRoute(String httpMethod, String path, String controllerName, String methodName) throws RouteException { Object controller = controllerLoader.load(controllerName); Class<?> controllerType = controller.getClass(); Method method = getMethod(controllerType, methodName); return new Route(HttpMethod.valueOf(httpMethod.toUpperCase()), path, controller, controllerType, method); } private Method getMethod(Class<?> controllerType, String methodName) throws RouteException { try { return controllerType.getMethod(methodName, Request.class, Response.class); } catch (Exception e) { throw new RouteException(e); } } public void setBasePackage(String basePackage) { this.controllerLoader = new ClassPathControllerLoader(basePackage); } public ControllerLoader getControllerLoader() { return controllerLoader; } public void setControllerLoader(ControllerLoader controllerLoader) { this.controllerLoader = controllerLoader; } }