Package com.elibom.jogger.middleware.router.loader

Source Code of com.elibom.jogger.middleware.router.loader.AbstractFileRoutesLoader

package com.elibom.jogger.middleware.router.loader;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import com.elibom.jogger.http.Request;
import com.elibom.jogger.http.Response;
import com.elibom.jogger.middleware.router.Route;
import com.elibom.jogger.middleware.router.RoutesException;
import com.elibom.jogger.middleware.router.Route.HttpMethod;

/**
* <p>Base class for classes that load routes from a file. Concrete implementations need only to
* implement the method {@link #getInputStream()}.</p>
*
* <h3>The format of the file</h3>
*
* <p>The file can have zero or more lines, which can be a <strong>route</strong>, a comment (starts with #) or a blank
* line. A <strong>route</strong> line has the following format:</p>
*
* <pre><code>
*   Route    := <em>HttpMethod</em> <em>Path</em> <em>Controller#Method</em>
*
*   HttpMethod  := (GET | POST | PUT | DELETE)
*   Path    := a valid path starting with /
*   Controller  := a string representing the name of the controller
*   Method    := a string representing the name of the method
* </code></pre>
*
* <p>For example:</p>
*
* <pre><code>
*   # users
*   GET    /users         com.app.controller.Users#index
*   POST   /users         com.app.controller.Users#create
*
*   # orders
*   GET    /orders/{id}   com.app.controller.Orders#show
* </code></pre>
*
* <p><em>Note:</em> tokens can be separated by one or more tabs/spaces.</p>
*
* @author German Escobar
*/
public abstract class AbstractFileRoutesLoader implements RoutesLoader {

  private ControllerLoader controllerLoader = new ClassPathControllerLoader();

  @Override
  public List<Route> load() throws ParseException, RoutesException {
    InputStream inputStream = null;
    try {
      inputStream = getInputStream();
    } catch (Exception e) {
      throw new RoutesException("Problem loading the routes.config file: " + e.getMessage(), e);
    }

    try {
      return load(inputStream);
    } catch (IOException e) {
      throw new RoutesException("Problem loading the routes.config file: " + e.getMessage(), e);
    }
  }

  /**
   * Helper method. Loads the routes from the <code>inputStream</code>.
   *
   * @param inputStream
   *
   * @return
   * @throws ParseException
   * @throws IOException
   */
  private List<Route> load(InputStream inputStream) throws ParseException, IOException {
    int line = 0; // reset line positioning
    List<Route> routes = new ArrayList<Route>(); // this is what we will fill and return

    BufferedReader in = null;
    try {
      in = new BufferedReader(new InputStreamReader(inputStream));

      String input;
      while ( (input = in.readLine()) != null ) {
        line++;

        input = input.trim();

        // only parse line if it is not empty and not a comment
        if (!input.equals("") && !input.startsWith("#")) {
          Route route = parse(input, line);
          routes.add(route);
        }
      }

    } finally {
      closeResource(in);
    }

    return routes;
  }

  /**
   * Helper method. Creates a {@link Route} object from the input string.
   *
   * @param input the string to parse.
   *
   * @return a {@link Route} object.
   * @throws ParseException if the line has an invalid format.
   */
  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);
    }

    // retrieve and validate the three arguments
    String httpMethod = validateHttpMethod( st.nextToken().trim(), line );
    String path = validatePath( st.nextToken().trim(), line );
    String controllerAndMethod = validateControllerAndMethod( st.nextToken().trim(), line );

    // retrieve controller name
    int hashPos = controllerAndMethod.indexOf('#');
    String controllerName = controllerAndMethod.substring(0, hashPos);

    // retrieve controller method
    String controllerMethod = controllerAndMethod.substring(hashPos + 1);

    return buildRoute(httpMethod, path, controllerName, controllerMethod);
  }

  /**
   * Helper method. It validates if the HTTP method is valid (i.e. is a GET, POST, PUT or DELETE).
   *
   * @param httpMethod the HTTP method to validate.
   *
   * @return the same httpMethod that was received as an argument.
   * @throws ParseException if the HTTP method is not recognized.
   */
  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;
  }

  /**
   * Helper method. It validates if the path is valid.
   *
   * @param path the path to be validated
   *
   * @return the same path that was received as an argument.
   * @throws ParseException if the path is not valid.
   */
  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;
  }

  /**
   * Helper method. Tells if a char is valid in a the path of a route line.
   *
   * @param c the char that we are validating.
   * @param openedKey if there is already an opened key ({) char before.
   *
   * @return true if the char is valid, false otherwise.
   */
  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;
  }

  /**
   * Helper method. Validates that the format of the controller and method is valid (i.e. in the form of controller#method).
   *
   * @param beanAndMethod the beanAndMethod string to be validated.
   *
   * @return the same beanAndMethod that was received as an argument.
   * @throws ParseException if the format of the controller and method is not valid.
   */
  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;
  }

  /**
   * Helper method. Builds a {@link Route} object from the received arguments instantiating the controller and the
   * method. It uses the {@link #loadController(String)} method that has to be defined by concrete implementations.
   *
   * @param httpMethod the HTTP method to which the route will respond.
   * @param path the HTTP path to which the route will respond.
   * @param controllerName the name of the controller that will handle this route.
   * @param methodName the name of the method that will handle this route.
   *
   * @return a {@link Route} object.
   * @throws RoutesException if there is a problem loading the controller or the method.
   */
  private Route buildRoute(String httpMethod, String path, String controllerName, String methodName) throws RoutesException {
    Object controller = controllerLoader.load(controllerName);
    Method method = getMethod(controller, methodName);

    return new Route(HttpMethod.valueOf(httpMethod.toUpperCase()), path, controller, method);
  }

  /**
   * Helper method. Retrieves the method with the specified <code>methodName</code> and from the specified object.
   * Notice that the method must received two parameters of types {@link Request} and {@link Response} respectively.
   *
   * @param controller the object from which we will retrieve the method.
   * @param methodName the name of the method to be retrieved.
   *
   * @return a <code>java.lang.reflect.Method</code> object.
   * @throws RoutesException if the method doesn't exists or there is a problem accessing the method.
   */
  private Method getMethod(Object controller, String methodName) throws RoutesException {
    try {
      // try to retrieve the method and check if an exception is thrown
      return controller.getClass().getMethod(methodName, Request.class, Response.class);
    } catch (Exception e) {
      throw new RoutesException(e);
    }
  }

  private void closeResource(Reader reader) {
    if (reader != null) {
      try {
        reader.close();
      } catch (Exception e) {
      }
    }
  }

  /**
   * Used to retrieve the InputStream
   *
   * @return
   * @throws Exception
   */
  protected abstract InputStream getInputStream() throws Exception;

  /**
   * Sets the <code>basePackage</code> to use when loading controllers (i.e you don't need to specified all the
   * package of all controllers in the routes files). This method will set a {@link ClassPathControllerLoader} as the
   * default mechanism to load controllers with the specified <code>basePackage</code>.
   *
   * @param basePackage the base package of all controllers.
   */
  public void setBasePackage(String basePackage) {
    this.controllerLoader = new ClassPathControllerLoader(basePackage);
  }

  public ControllerLoader getControllerLoader() {
    return controllerLoader;
  }

  public void setControllerLoader(ControllerLoader controllerLoader) {
    this.controllerLoader = controllerLoader;
  }

}
TOP

Related Classes of com.elibom.jogger.middleware.router.loader.AbstractFileRoutesLoader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.