com.github.wnameless.spring.routing.RoutingPathResolver.java Source code

Java tutorial

Introduction

Here is the source code for com.github.wnameless.spring.routing.RoutingPathResolver.java

Source

  /*
   *
   * Copyright 2015 Wei-Ming Wu
   *
   * 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 com.github.wnameless.spring.routing;

  import static com.google.common.collect.Lists.newArrayList;
  import static com.google.common.collect.Sets.newLinkedHashSet;
  import static net.sf.rubycollect4j.RubyCollections.hp;
  import static net.sf.rubycollect4j.RubyCollections.ra;

  import java.lang.annotation.Annotation;
  import java.lang.reflect.Method;
  import java.util.ArrayList;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  import java.util.Set;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;

  import org.springframework.context.ApplicationContext;
  import org.springframework.core.env.Environment;
  import org.springframework.stereotype.Controller;
  import org.springframework.web.bind.annotation.RequestMapping;
  import org.springframework.web.bind.annotation.RequestMethod;
  import org.springframework.web.bind.annotation.RestController;

  import com.github.wnameless.regex.Regexs;

  import net.sf.rubycollect4j.RubyArray;
  import net.sf.rubycollect4j.block.BooleanBlock;

  /**
   * 
   * {@link RoutingPathResolver} searches all Spring annotated routing paths under
   * given package bases which are provided by {@link RequestMapping} annotations
   * into a list of {@link RoutingPath} objects.
   *
   */
  public final class RoutingPathResolver {

      private static final Pattern PLACEHOLDER = Pattern.compile("\\$\\{[^}]+\\}");
      private static final Pattern PATH_VAR = Pattern.compile("\\{[^}]+\\}");
      private static final Pattern ANT_AA = Pattern.compile("\\*\\*");
      private static final Pattern ANT_A = Pattern.compile("\\*");
      private static final Pattern ANT_Q = Pattern.compile("\\?");

      private final Environment env;
      private final Set<RoutingPath> routingPaths = newLinkedHashSet();

      /**
       * Creates a {@link RoutingPathResolver}.
       * 
       * @param appCtx
       *          the Spring {@link ApplicationContext}
       * @param basePackages
       *          packages to be searched
       */
      public RoutingPathResolver(ApplicationContext appCtx, String... basePackages) {
          env = appCtx.getEnvironment();
          Map<String, Object> beans = appCtx.getBeansWithAnnotation(Controller.class);
          beans.putAll(appCtx.getBeansWithAnnotation(RestController.class));
          retainBeansByPackageNames(beans, basePackages);

          for (Object bean : beans.values()) {
              List<Method> mappingMethods = getMethodsListWithAnnotation(bean.getClass(), RequestMapping.class);
              RequestMapping classRM = bean.getClass().getAnnotation(RequestMapping.class);
              for (Method method : mappingMethods) {
                  RequestMapping methodRM = method.getAnnotation(RequestMapping.class);
                  for (Entry<String, RequestMethod> rawPathAndMethod : computeRawPaths(classRM, methodRM)) {
                      String rawPath = rawPathAndMethod.getKey();
                      String path = computePath(rawPath);
                      String regexPath = computeRegexPath(path);
                      routingPaths.add(new RoutingPath(rawPathAndMethod.getValue(), rawPath, path,
                              Pattern.compile(regexPath), bean.getClass().getAnnotations(), method.getAnnotations()));
                  }
              }
          }
      }

      /**
       * Returns a list of {@link RoutingPath} under given package bases.
       * 
       * @return a list of {@link RoutingPath}
       */
      public List<RoutingPath> getRoutingPaths() {
          return newArrayList(routingPaths);
      }

/**
 * Finds {@link RoutingPath}s by given annotation which may show on class or
 * method level of a {@link RequestMapping}.
 * 
 * @param annoType
 *          the class of an annotation
 * @return founded {@link RoutingPath}
 */
public List<RoutingPath> findByAnnotationType(
    final Class<? extends Annotation> annoType) {
  return ra(routingPaths).keepIf(new BooleanBlock<RoutingPath>() {

    @Override
    public boolean yield(RoutingPath item) {
      return ra(item.getClassAnnotations())
          .any(new BooleanBlock<Annotation>() {

        @Override
        public boolean yield(Annotation item) {
          return annoType.equals(item.annotationType());
        }

      }) || ra(item.getMethodAnnotations())
          .any(new BooleanBlock<Annotation>() {

        @Override
        public boolean yield(Annotation item) {
          return annoType.equals(item.annotationType());
        }

      });
    }

  }).toA();
}

/**
 * Finds {@link RoutingPath}s by given annotation which only show on class
 * level of a {@link RequestMapping}.
 * 
 * @param annoType
 *          the class of an annotation
 * @return founded {@link RoutingPath}
 */
public List<RoutingPath> findByClassAnnotationType(
    final Class<? extends Annotation> annoType) {
  return ra(routingPaths).keepIf(new BooleanBlock<RoutingPath>() {

    @Override
    public boolean yield(RoutingPath item) {
      return ra(item.getClassAnnotations())
          .any(new BooleanBlock<Annotation>() {

        @Override
        public boolean yield(Annotation item) {
          return annoType.equals(item.annotationType());
        }

      });
    }

  }).toA();
}

/**
 * Finds {@link RoutingPath}s by given annotation which only show on method
 * level of a {@link RequestMapping}.
 * 
 * @param annoType
 *          the class of an annotation
 * @return founded {@link RoutingPath}
 */
public List<RoutingPath> findByMethodAnnotationType(
    final Class<? extends Annotation> annoType) {
  return ra(routingPaths).keepIf(new BooleanBlock<RoutingPath>() {

    @Override
    public boolean yield(RoutingPath item) {
      return ra(item.getMethodAnnotations())
          .any(new BooleanBlock<Annotation>() {

        @Override
        public boolean yield(Annotation item) {
          return annoType.equals(item.annotationType());
        }

      });
    }

  }).toA();
}

      /**
       * Finds {@link RoutingPath}s by given path and request method.
       * 
       * @param requestPath
       *          to be found
       * @param method
       *          to be matched
       * @return founded {@link RoutingPath}
       */
      public RoutingPath findByRequestPathAndMethod(String requestPath, RequestMethod method) {
          for (RoutingPath routingPath : routingPaths) {
              if (routingPath.getPath().equals(requestPath) && routingPath.getMethod().equals(method))
                  return routingPath;
          }

          for (RoutingPath routingPath : routingPaths) {
              if (requestPath.matches(routingPath.getRegexPath().pattern()) && routingPath.getMethod().equals(method))
                  return routingPath;
          }

          return null;
      }

      /**
       * Finds {@link RoutingPath}s by given path.
       * 
       * @param requestPath
       *          to be found
       * @return founded {@link RoutingPath}
       */
      public List<RoutingPath> findByRequestPath(String requestPath) {
          List<RoutingPath> paths = newArrayList();

          for (RoutingPath routingPath : routingPaths) {
              if (routingPath.getPath().equals(requestPath)) {
                  paths.add(routingPath);
              } else if (requestPath.matches(routingPath.getRegexPath().pattern())) {
                  paths.add(routingPath);
              }
          }

          return paths;
      }

private List<Entry<String, RequestMethod>> computeRawPaths(
    RequestMapping classRM, RequestMapping methodRM) {
  List<Entry<String, RequestMethod>> rawPathsAndMethods = newArrayList();

  RubyArray<String> topPaths =
      classRM == null ? ra("") : ra(classRM.value()).uniq();
  RubyArray<String> lowPaths = ra(methodRM.value()).uniq();
  if (topPaths.isEmpty()) topPaths.unshift("");
  if (lowPaths.isEmpty()) lowPaths.unshift("");

  while (topPaths.any()) {
    String topPath = topPaths.shift();
    while (lowPaths.any()) {
      String lowPath = lowPaths.shift();
      if (methodRM.method().length == 0) {
        for (RequestMethod m : RequestMethod.values()) {
          rawPathsAndMethods
              .add(hp(PathUtils.joinPaths(topPath, lowPath), m));
        }
      } else {
        for (RequestMethod m : methodRM.method()) {
          rawPathsAndMethods
              .add(hp(PathUtils.joinPaths(topPath, lowPath), m));
        }
      }
    }
  }

  return rawPathsAndMethods;
}

      private String computeRegexPath(String path) {
          path = Regexs.escapeSpecialCharacters(path, PLACEHOLDER, PATH_VAR, ANT_AA, ANT_A, ANT_Q);
          Matcher m = PATH_VAR.matcher(path);
          while (m.find()) {
              String match = m.group();
              path = path.replaceFirst(Pattern.quote(match), "[^/]+");
          }
          m = ANT_AA.matcher(path);
          while (m.find()) {
              String match = m.group();
              path = path.replaceFirst(Pattern.quote(match), ".\"");
          }
          m = ANT_A.matcher(path);
          while (m.find()) {
              String match = m.group();
              path = path.replaceFirst(Pattern.quote(match), "[^/]*");
          }
          m = ANT_Q.matcher(path);
          while (m.find()) {
              String match = m.group();
              path = path.replaceFirst(Pattern.quote(match), ".");
          }
          path = path.replaceAll(Pattern.quote("\""), "*");

          // the first slash of an URL can be omitted
          path = path.startsWith("/") ? path = "/?" + path.substring(1) : "/?" + path;
          // the last slash of an URL is optional if user not mentions
          if (!path.endsWith("/"))
              path = path + "/?";

          return path;
      }

      private String computePath(String rawPath) {
          String path = rawPath;
          Matcher m = PLACEHOLDER.matcher(rawPath);
          while (m.find()) {
              String placeholder = m.group();
              String trimmedPlaceholder = placeholder.substring(2, placeholder.length() - 1);
              String[] keyAndDefault = trimmedPlaceholder.split(":");
              String key = keyAndDefault[0];
              String deFault = "";
              if (keyAndDefault.length > 1)
                  deFault = keyAndDefault[1];
              path = path.replaceFirst(Pattern.quote(placeholder), env.getProperty(key, deFault));
          }
          return path;
      }

      private void retainBeansByPackageNames(Map<String, Object> beans, String... basePackages) {
          Iterator<Object> beansIter = beans.values().iterator();
          while (beansIter.hasNext()) {
              String beanPackage = beansIter.next().getClass().getPackage().getName();
              boolean isKeep = false;
              for (String packageName : basePackages) {
                  if (beanPackage.equals(packageName) || beanPackage.startsWith(packageName + ".")) {
                      isKeep = true;
                  }
              }
              if (!isKeep)
                  beansIter.remove();
          }
      }

      private List<Method> getMethodsListWithAnnotation(final Class<?> cls,
              final Class<? extends Annotation> annotationCls) {
          Method[] allMethods = cls.getDeclaredMethods();
          List<Method> annotatedMethods = new ArrayList<Method>();
          for (Method method : allMethods) {
              if (method.getAnnotation(annotationCls) != null) {
                  annotatedMethods.add(method);
              }
          }
          return annotatedMethods;
      }

  }