001/**
002 * Copyright 2005-2015 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.web.controller;
017
018import org.springframework.util.LinkedMultiValueMap;
019import org.springframework.util.MultiValueMap;
020import org.springframework.web.method.HandlerMethod;
021import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
022import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
023
024import javax.servlet.http.HttpServletRequest;
025import java.lang.reflect.Method;
026import java.util.ArrayList;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.Comparator;
030import java.util.LinkedHashMap;
031import java.util.List;
032import java.util.Map;
033import java.util.Set;
034
035/**
036 * Overriding of provided spring handler mapping to allow controllers with the same mapping to exist, and override
037 * if necessary.
038 *
039 * @author Kuali Rice Team (rice.collab@kuali.org)
040 */
041public class UifRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
042
043    private final Map<RequestMappingInfo, HandlerMethod> handlerMethods =
044            new LinkedHashMap<RequestMappingInfo, HandlerMethod>();
045
046    private final MultiValueMap<String, RequestMappingInfo> urlMap =
047            new LinkedMultiValueMap<String, RequestMappingInfo>();
048
049    /**
050     * {@inheritDoc}
051     */
052    @Override
053    public Map<RequestMappingInfo, HandlerMethod> getHandlerMethods() {
054        return Collections.unmodifiableMap(this.handlerMethods);
055    }
056
057    /**
058     * Override to only populate the first handler given for a mapping.
059     *
060     * {@inheritDoc}
061     */
062    @Override
063    protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
064        HandlerMethod newHandlerMethod = super.createHandlerMethod(handler, method);
065
066        this.handlerMethods.put(mapping, newHandlerMethod);
067        if (logger.isInfoEnabled()) {
068            logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod);
069        }
070
071        if (!this.handlerMethods.containsKey(mapping)) {
072            Set<String> patterns = super.getMappingPathPatterns(mapping);
073            for (String pattern : patterns) {
074                if (!super.getPathMatcher().isPattern(pattern)) {
075                    this.urlMap.add(pattern, mapping);
076                }
077            }
078        }
079    }
080
081    /**
082     * {@inheritDoc}
083     */
084    @Override
085    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
086        List<Match> matches = new ArrayList<Match>();
087
088        List<RequestMappingInfo> directPathMatches = this.urlMap.get(lookupPath);
089        if (directPathMatches != null) {
090            addMatchingMappings(directPathMatches, matches, request);
091        }
092
093        if (matches.isEmpty()) {
094            // No choice but to go through all mappings
095            addMatchingMappings(this.handlerMethods.keySet(), matches, request);
096        }
097
098        if (!matches.isEmpty()) {
099            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}