com.foreach.common.web.mapper.SubClassAllowingRequestMappingHandlerMapping Maven / Gradle / Ivy
Show all versions of common-web Show documentation
/*
* Copyright 2014 the original author or authors
*
* 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.foreach.common.web.mapper;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContextException;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;
/**
* Abstract base class for {@link org.springframework.web.servlet.HandlerMapping} implementations that define a
* mapping between a request and a {@link org.springframework.web.method.HandlerMethod}.
*
* For each registered handler method, a unique mapping is maintained with
* subclasses defining the details of the mapping type {@code }.
*
* @deprecated because using this mapping would encourage having multiple singleton beans whereas only
* one would be used. Better solution is to ensure only one controller exists.
*/
@Deprecated
public class SubClassAllowingRequestMappingHandlerMapping extends RequestMappingHandlerMapping
{
private boolean detectHandlerMethodsInAncestorContexts = false;
private final Map handlerMethods =
new LinkedHashMap();
private final MultiValueMap urlMap =
new LinkedMultiValueMap();
/**
* Whether to detect handler methods in beans in ancestor ApplicationContexts.
* Default is "false": Only beans in the current ApplicationContext are
* considered, i.e. only in the context that this HandlerMapping itself
* is defined in (typically the current DispatcherServlet's context).
*
Switch this flag on to detect handler beans in ancestor contexts
* (typically the Spring root WebApplicationContext) as well.
*/
public void setDetectHandlerMethodsInAncestorContexts( boolean detectHandlerMethodsInAncestorContexts ) {
this.detectHandlerMethodsInAncestorContexts = detectHandlerMethodsInAncestorContexts;
}
/**
* Return a map with all handler methods and their mappings.
*/
public Map getHandlerMethods() {
return Collections.unmodifiableMap( handlerMethods );
}
/**
* ApplicationContext initialization and handler method detection.
*/
@Override
public void initApplicationContext() throws ApplicationContextException {
super.initApplicationContext();
initHandlerMethods();
}
/**
* Scan beans in the ApplicationContext, detect and register handler methods.
*
* @see #isHandler(Class)
* @see #getMappingForMethod(java.lang.reflect.Method, Class)
* @see #handlerMethodsInitialized(java.util.Map)
*/
protected void initHandlerMethods() {
if ( logger.isDebugEnabled() ) {
logger.debug( "Looking for request mappings in application context: " + getApplicationContext() );
}
String[] beanNames =
( this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
getApplicationContext(), Object.class ) : getApplicationContext().getBeanNamesForType(
Object.class ) );
for ( String beanName : beanNames ) {
if ( isHandler( getApplicationContext().getType( beanName ) ) ) {
detectHandlerMethods( beanName );
}
}
handlerMethodsInitialized( getHandlerMethods() );
}
/**
* Register a handler method and its unique mapping.
*
* @param handler the bean name of the handler or the handler instance
* @param method the method to register
* @param mapping the mapping conditions associated with the handler method
* @throws IllegalStateException if another method was already registered
* under the same mapping
*/
@Override
protected void registerHandlerMethod( Object handler, Method method, RequestMappingInfo mapping ) {
HandlerMethod handlerMethod;
if ( handler instanceof String ) {
String beanName = (String) handler;
handlerMethod = new HandlerMethod( beanName, getApplicationContext(), method );
}
else {
handlerMethod = new HandlerMethod( handler, method );
}
HandlerMethod oldHandlerMethod = handlerMethods.get( mapping );
if ( oldHandlerMethod != null ) {
if ( oldHandlerMethod.equals( handlerMethod ) || handlerMethod.getBeanType().isAssignableFrom(
oldHandlerMethod.getBeanType() ) ) {
return;
}
else if ( !oldHandlerMethod.getBeanType().isAssignableFrom( handlerMethod.getBeanType() ) ) {
throw new IllegalStateException(
"Ambiguous mapping found. Cannot map '" + handlerMethod.getBean() + "' bean method \n" + handlerMethod + "\nto " +
mapping + ": There is already '" + oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped and one is not a subclass of the other." );
}
else {
Set patterns = getMappingPathPatterns( mapping );
for ( String pattern : patterns ) {
if ( !getPathMatcher().isPattern( pattern ) && urlMap.containsKey( pattern ) ) {
urlMap.remove( pattern );
}
}
}
}
handlerMethods.put( mapping, handlerMethod );
if ( logger.isInfoEnabled() ) {
logger.info( "Mapped \"" + mapping + "\" onto " + handlerMethod );
}
Set patterns = getMappingPathPatterns( mapping );
for ( String pattern : patterns ) {
if ( !getPathMatcher().isPattern( pattern ) ) {
urlMap.add( pattern, mapping );
}
}
}
/**
* Look up a handler method for the given request.
*/
@Override
protected HandlerMethod getHandlerInternal( HttpServletRequest request ) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest( request );
if ( logger.isDebugEnabled() ) {
logger.debug( "Looking up handler method for path " + lookupPath );
}
HandlerMethod handlerMethod = lookupHandlerMethod( lookupPath, request );
if ( logger.isDebugEnabled() ) {
if ( handlerMethod != null ) {
logger.debug( "Returning handler method [" + handlerMethod + "]" );
}
else {
logger.debug( "Did not find handler method for [" + lookupPath + "]" );
}
}
return ( handlerMethod != null ) ? handlerMethod.createWithResolvedBean() : null;
}
/**
* Look up the best-matching handler method for the current request.
* If multiple matches are found, the best match is selected.
*
* @param lookupPath mapping lookup path within the current servlet mapping
* @param request the current request
* @return the best-matching handler method, or {@code null} if no match
* @see #handleMatch(Object, String, javax.servlet.http.HttpServletRequest)
* @see #handleNoMatch(java.util.Set, String, javax.servlet.http.HttpServletRequest)
*/
protected HandlerMethod lookupHandlerMethod( String lookupPath, HttpServletRequest request ) throws Exception {
List matches = new ArrayList();
List directPathMatches = this.urlMap.get( lookupPath );
if ( directPathMatches != null ) {
addMatchingMappings( directPathMatches, matches, request );
}
if ( matches.isEmpty() ) {
// No choice but to go through all mappings
addMatchingMappings( this.handlerMethods.keySet(), matches, request );
}
if ( !matches.isEmpty() ) {
Comparator comparator = new MatchComparator( getMappingComparator( request ) );
Collections.sort( matches, comparator );
if ( logger.isTraceEnabled() ) {
logger.trace(
"Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches );
}
Match bestMatch = matches.get( 0 );
if ( matches.size() > 1 ) {
Match secondBestMatch = matches.get( 1 );
if ( comparator.compare( bestMatch, secondBestMatch ) == 0 ) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
throw new IllegalStateException(
"Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" +
m1 + ", " + m2 + "}" );
}
}
handleMatch( bestMatch.mapping, lookupPath, request );
return bestMatch.handlerMethod;
}
else {
return handleNoMatch( handlerMethods.keySet(), lookupPath, request );
}
}
private void addMatchingMappings(
Collection mappings,
List matches,
HttpServletRequest request ) {
for ( RequestMappingInfo mapping : mappings ) {
RequestMappingInfo match = getMatchingMapping( mapping, request );
if ( match != null ) {
matches.add( new Match( match, handlerMethods.get( mapping ) ) );
}
}
}
/**
* A temporary container for a mapping matched to a request.
*/
private class Match
{
private final RequestMappingInfo mapping;
private final HandlerMethod handlerMethod;
private Match( RequestMappingInfo mapping, HandlerMethod handlerMethod ) {
this.mapping = mapping;
this.handlerMethod = handlerMethod;
}
@Override
public String toString() {
return mapping.toString();
}
}
private class MatchComparator implements Comparator
{
private final Comparator comparator;
public MatchComparator( Comparator comparator ) {
this.comparator = comparator;
}
public int compare( Match match1, Match match2 ) {
return comparator.compare( match1.mapping, match2.mapping );
}
}
}