PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/atlassian-pretty-urls-plugin/src/main/java/com/atlassian/prettyurls/internal/route/UrlRouterImpl.java

https://bitbucket.org/atlassian/atlassian-pretty-urls
Java | 202 lines | 147 code | 29 blank | 26 comment | 13 complexity | a59c7c4bdb2079a7b7847920016d1604 MD5 | raw file
  1. package com.atlassian.prettyurls.internal.route;
  2. import com.atlassian.ozymandias.SafePluginPointAccess;
  3. import com.atlassian.plugin.servlet.filter.FilterLocation;
  4. import com.atlassian.prettyurls.api.route.RoutePredicate;
  5. import com.atlassian.prettyurls.api.route.RouteService;
  6. import com.atlassian.prettyurls.api.route.UrlRouteRule;
  7. import com.atlassian.prettyurls.api.route.UrlRouteRuleSet;
  8. import com.google.common.annotations.VisibleForTesting;
  9. import com.google.common.collect.Maps;
  10. import org.slf4j.Logger;
  11. import org.slf4j.LoggerFactory;
  12. import org.springframework.beans.factory.annotation.Autowired;
  13. import org.springframework.stereotype.Component;
  14. import javax.servlet.http.HttpServletRequest;
  15. import javax.ws.rs.core.UriBuilder;
  16. import java.net.URI;
  17. import java.net.URISyntaxException;
  18. import java.util.Collections;
  19. import java.util.List;
  20. import java.util.Map;
  21. import java.util.Set;
  22. import static com.atlassian.prettyurls.internal.route.UrlMatcher.Strategy;
  23. import static com.atlassian.prettyurls.internal.util.UrlUtils.startWithSlash;
  24. import static java.util.stream.Collectors.toList;
  25. import static java.util.stream.Collectors.toSet;
  26. /**
  27. * The implementation of URL routing
  28. */
  29. @Component
  30. public class UrlRouterImpl implements UrlRouter {
  31. private final static Logger log = LoggerFactory.getLogger(UrlRouterImpl.class);
  32. private final RouteService routeService;
  33. @Autowired
  34. public UrlRouterImpl(final RouteService routeService) {
  35. this.routeService = routeService;
  36. }
  37. @Override
  38. public Result route(HttpServletRequest httpRequest, FilterLocation filterLocation) {
  39. String requestURI;
  40. try {
  41. requestURI = makeRequestURI(httpRequest);
  42. } catch (URISyntaxException e) {
  43. log.error("Failed to create requestUri {} due to: {}", httpRequest.getRequestURI(), e.getMessage());
  44. return new Result(null, false);
  45. }
  46. Set<UrlRouteRuleSet> urlRouteRuleSets = getRouteRuleSets(filterLocation, requestURI);
  47. //
  48. // filter away disabled rule sets
  49. urlRouteRuleSets = urlRouteRuleSets.stream()
  50. .filter(ruleSet -> ruleSetEnabled(httpRequest, ruleSet))
  51. .collect(toSet());
  52. //
  53. // if we have multiple competing rule sets for the same top level request then we have a route resolution
  54. // challenge and we move to JAX-RS matching
  55. //
  56. // https://jsr311.java.net//nonav/releases/1.0/spec/spec3.html#x3-350003.7.2
  57. //
  58. final Strategy matchStrategy = urlRouteRuleSets.size() > 1 ? Strategy.JAX_RS_MATCHING : Strategy.LIST_ORDER_MATCHING;
  59. final List<UrlRouteRule> urlRouteRules = urlRouteRuleSets
  60. .stream()
  61. //
  62. // now collect all the rules in the rule set
  63. .flatMap(ruleSet -> ruleSet.getUrlRouteRules().stream())
  64. //
  65. // filter away the disabled rules
  66. .filter(rule -> ruleEnabled(httpRequest, rule))
  67. //
  68. // filter away the non matching http verbs
  69. .filter(rule -> httpVerbsMatch(httpRequest, rule))
  70. //
  71. // and we have our list of enabled rules
  72. .collect(toList());
  73. final UrlMatcher.Result matchResult = new UrlMatcher().getMatchingRule(requestURI, urlRouteRules, matchStrategy);
  74. if (matchResult.matches()) {
  75. // we have a winner
  76. String toURI = buildToURI(matchResult.getMatchingRule().get(), httpRequest, matchResult.getParsedVariableValues());
  77. return new Result(toURI, true);
  78. }
  79. return new Result(null, false);
  80. }
  81. private boolean httpVerbsMatch(HttpServletRequest httpRequest, UrlRouteRule urlRouteRule) {
  82. List<String> httpVerbs = urlRouteRule.getHttpVerbs();
  83. if (httpVerbs.size() == 0) {
  84. return true;
  85. }
  86. String method = httpRequest.getMethod();
  87. method = (method == null) ? "" : method.toUpperCase();
  88. for (String httpVerb : httpVerbs) {
  89. if (method.equals(httpVerb)) {
  90. return true;
  91. }
  92. }
  93. return false;
  94. }
  95. private String buildToURI(UrlRouteRule urlRouteRule, HttpServletRequest request, Map<String, String> parsedFromValues) {
  96. UrlRouteRule.ParameterMode parameterMode = urlRouteRule.getParameterMode();
  97. String toURI = urlRouteRule.getToUriGenerator().generate(request, parsedFromValues);
  98. Map<String, String> parametersToPass;
  99. switch (parameterMode) {
  100. case PASS_ALL:
  101. parametersToPass = parsedFromValues;
  102. break;
  103. case PASS_UNMAPPED:
  104. Map<String, String> unmappedFromValues = Maps.newHashMap(parsedFromValues);
  105. if (urlRouteRule.getTo() != null) {
  106. for (String variable : urlRouteRule.getTo().getTemplateVariables()) {
  107. unmappedFromValues.remove(variable);
  108. }
  109. }
  110. parametersToPass = unmappedFromValues;
  111. break;
  112. case PASS_NONE:
  113. parametersToPass = Collections.emptyMap();
  114. break;
  115. default:
  116. throw new IllegalArgumentException("Unrecognized " + UrlRouteRule.ParameterMode.class.getSimpleName() + " value: " + parameterMode);
  117. }
  118. //
  119. // add all the parameters that are left into the destination URL as query parameters
  120. // and then that is what we redirect to. RequestDispatcher handles the fact that
  121. // the original query parameters are retained.
  122. //
  123. UriBuilder uriBuilder = UriBuilder.fromUri(startWithSlash(toURI));
  124. for (Map.Entry<String, String> entry : parametersToPass.entrySet()) {
  125. uriBuilder.queryParam(entry.getKey(), entry.getValue());
  126. }
  127. toURI = uriBuilder.build().toString();
  128. return toURI;
  129. }
  130. private boolean ruleSetEnabled(final HttpServletRequest httpRequest, final UrlRouteRuleSet urlRouteRuleSet) {
  131. final RoutePredicate<UrlRouteRuleSet> predicate = urlRouteRuleSet.getPredicate();
  132. return runPredicateSafely(httpRequest, urlRouteRuleSet, predicate);
  133. }
  134. private boolean ruleEnabled(final HttpServletRequest httpRequest, final UrlRouteRule urlRouteRule) {
  135. final RoutePredicate<UrlRouteRule> predicate = urlRouteRule.getPredicate();
  136. return runPredicateSafely(httpRequest, urlRouteRule, predicate);
  137. }
  138. private <T> boolean runPredicateSafely(final HttpServletRequest httpRequest, final T rule, final RoutePredicate<T> predicate) {
  139. // we protect against dodgy plugin code here by using ozymandias. Its not enabled if it throws an exception during predicate evaluation
  140. Boolean result = SafePluginPointAccess.to().callable(() -> predicate.apply(httpRequest, rule));
  141. return result == null ? false : result;
  142. }
  143. @VisibleForTesting
  144. Set<UrlRouteRuleSet> getRouteRuleSets(FilterLocation filterLocation, String requestURI) {
  145. return routeService.getRouteRuleSets(filterLocation, requestURI);
  146. }
  147. @VisibleForTesting
  148. String makeRequestURI(HttpServletRequest httpServletRequest)
  149. throws URISyntaxException {
  150. String requestURI = new URI(httpServletRequest.getRequestURI()).normalize().toString();
  151. String context = httpServletRequest.getContextPath();
  152. if (requestURI.startsWith(context)) {
  153. requestURI = requestURI.substring(context.length());
  154. }
  155. return requestURI;
  156. }
  157. static class Result implements UrlRouter.Result {
  158. private final String toURI;
  159. private final boolean routed;
  160. private Result(String toURI, boolean routed) {
  161. this.toURI = toURI;
  162. this.routed = routed;
  163. }
  164. @Override
  165. public String toURI() {
  166. return toURI;
  167. }
  168. @Override
  169. public boolean isRouted() {
  170. return routed;
  171. }
  172. }
  173. }