View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.web.controller;
17  
18  import org.springframework.util.LinkedMultiValueMap;
19  import org.springframework.util.MultiValueMap;
20  import org.springframework.web.method.HandlerMethod;
21  import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
22  import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
23  
24  import javax.servlet.http.HttpServletRequest;
25  import java.lang.reflect.Method;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.Comparator;
30  import java.util.LinkedHashMap;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  
35  /**
36   * Overriding of provided spring handler mapping to allow controllers with the same mapping to exist, and override
37   * if necessary.
38   *
39   * @author Kuali Rice Team (rice.collab@kuali.org)
40   */
41  public class UifRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
42  
43      private final Map<RequestMappingInfo, HandlerMethod> handlerMethods =
44              new LinkedHashMap<RequestMappingInfo, HandlerMethod>();
45  
46      private final MultiValueMap<String, RequestMappingInfo> urlMap =
47              new LinkedMultiValueMap<String, RequestMappingInfo>();
48  
49      /**
50       * {@inheritDoc}
51       */
52      @Override
53      public Map<RequestMappingInfo, HandlerMethod> getHandlerMethods() {
54          return Collections.unmodifiableMap(this.handlerMethods);
55      }
56  
57      /**
58       * Override to only populate the first handler given for a mapping.
59       *
60       * {@inheritDoc}
61       */
62      @Override
63      protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
64          HandlerMethod newHandlerMethod = super.createHandlerMethod(handler, method);
65  
66          this.handlerMethods.put(mapping, newHandlerMethod);
67          if (logger.isInfoEnabled()) {
68              logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod);
69          }
70  
71          if (!this.handlerMethods.containsKey(mapping)) {
72              Set<String> patterns = super.getMappingPathPatterns(mapping);
73              for (String pattern : patterns) {
74                  if (!super.getPathMatcher().isPattern(pattern)) {
75                      this.urlMap.add(pattern, mapping);
76                  }
77              }
78          }
79      }
80  
81      /**
82       * {@inheritDoc}
83       */
84      @Override
85      protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
86          List<Match> matches = new ArrayList<Match>();
87  
88          List<RequestMappingInfo> directPathMatches = this.urlMap.get(lookupPath);
89          if (directPathMatches != null) {
90              addMatchingMappings(directPathMatches, matches, request);
91          }
92  
93          if (matches.isEmpty()) {
94              // No choice but to go through all mappings
95              addMatchingMappings(this.handlerMethods.keySet(), matches, request);
96          }
97  
98          if (!matches.isEmpty()) {
99              Comparator<Match> comparator = new MatchComparator(super.getMappingComparator(request));
100             Collections.sort(matches, comparator);
101 
102             if (logger.isTraceEnabled()) {
103                 logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
104             }
105 
106             Match bestMatch = matches.get(0);
107             if (matches.size() > 1) {
108                 Match secondBestMatch = matches.get(1);
109                 if (comparator.compare(bestMatch, secondBestMatch) == 0) {
110                     Method m1 = bestMatch.handlerMethod.getMethod();
111                     Method m2 = secondBestMatch.handlerMethod.getMethod();
112                     throw new IllegalStateException(
113                             "Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" +
114                                     m1 + ", " + m2 + "}");
115                 }
116             }
117 
118             handleMatch(bestMatch.mapping, lookupPath, request);
119             return bestMatch.handlerMethod;
120         } else {
121             return handleNoMatch(handlerMethods.keySet(), lookupPath, request);
122         }
123     }
124 
125 
126     private void addMatchingMappings(Collection<RequestMappingInfo> mappings, List<Match> matches,
127             HttpServletRequest request) {
128         for (RequestMappingInfo mapping : mappings) {
129             RequestMappingInfo match = getMatchingMapping(mapping, request);
130             if (match != null) {
131                 matches.add(new Match(match, handlerMethods.get(mapping)));
132             }
133         }
134     }
135 
136     private static class Match {
137         private final RequestMappingInfo mapping;
138         private final HandlerMethod handlerMethod;
139 
140         private Match(RequestMappingInfo mapping, HandlerMethod handlerMethod) {
141             this.mapping = mapping;
142             this.handlerMethod = handlerMethod;
143         }
144 
145         @Override
146         public String toString() {
147             return this.mapping.toString();
148         }
149     }
150 
151     private static class MatchComparator implements Comparator<Match> {
152         private final Comparator<RequestMappingInfo> comparator;
153 
154         public MatchComparator(Comparator<RequestMappingInfo> comparator) {
155             this.comparator = comparator;
156         }
157 
158         public int compare(Match match1, Match match2) {
159             return this.comparator.compare(match1.mapping, match2.mapping);
160         }
161     }
162 }