package com.hundsun.jresplus.web.adapter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; import java.lang.reflect.Method; import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.config.BeanExpressionContext; import org.springframework.beans.factory.config.BeanExpressionResolver; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.core.Ordered; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.xml.SourceHttpMessageConverter; import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.ui.ExtendedModelMap; import org.springframework.ui.Model; import org.springframework.util.AntPathMatcher; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.PathMatcher; import org.springframework.util.StringUtils; import org.springframework.validation.support.BindingAwareModelMap; import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.HttpSessionRequiredException; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.ServletRequestDataBinder; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.bind.annotation.support.HandlerMethodInvoker; import org.springframework.web.bind.annotation.support.HandlerMethodResolver; import org.springframework.web.bind.support.DefaultSessionAttributeStore; import org.springframework.web.bind.support.SessionAttributeStore; import org.springframework.web.bind.support.WebArgumentResolver; import org.springframework.web.bind.support.WebBindingInitializer; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.RequestScope; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.multipart.MultipartRequest; import org.springframework.web.servlet.HandlerAdapter; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.View; import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter; import org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver; import org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver; import org.springframework.web.servlet.mvc.multiaction.MethodNameResolver; import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException; import org.springframework.web.servlet.support.RequestContextUtils; import org.springframework.web.servlet.support.WebContentGenerator; import org.springframework.web.util.UrlPathHelper; import org.springframework.web.util.WebUtils; import com.hundsun.jresplus.beans.ObjectFactory; import com.hundsun.jresplus.common.util.ArrayUtil; import com.hundsun.jresplus.web.servlet.MediaTypesHandler; /** * @author LeoHu copy by sagahl copy by fish */ public class ExtendableAnnotationMethodHandlerAdapter extends WebContentGenerator implements HandlerAdapter, Ordered, BeanFactoryAware, InitializingBean { /** * Log category to use when no mapped handler is found for a request. * * @see #pageNotFoundLogger */ public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound"; /** * Additional logger to use when no mapped handler is found for a request. * * @see #PAGE_NOT_FOUND_LOG_CATEGORY */ protected static final Log pageNotFoundLogger = LogFactory .getLog(PAGE_NOT_FOUND_LOG_CATEGORY); private UrlPathHelper urlPathHelper = new UrlPathHelper(); private PathMatcher pathMatcher = new AntPathMatcher(); private MethodNameResolver methodNameResolver = new InternalPathMethodNameResolver(); private boolean webVaild = false; private WebBindingInitializer webBindingInitializer; private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore(); private int cacheSecondsForSessionAttributeHandlers = 0; private boolean synchronizeOnSession = false; private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); private WebArgumentResolver[] customArgumentResolvers; private ModelAndViewResolver[] customModelAndViewResolvers; private ReturnValueHandlerResolver customReturnValueHandlerResolver; private HttpMessageConverter<?>[] messageConverters; private int order = Ordered.LOWEST_PRECEDENCE; private ConfigurableBeanFactory beanFactory; private BeanExpressionContext expressionContext; private final Map<Class<?>, ServletHandlerMethodResolver> methodResolverCache = new HashMap<Class<?>, ServletHandlerMethodResolver>(); private List<AnnotationMethodHandlerInterceptor> methodInvokeInterceptors; private static final List<MediaType> emptyEmdiaTyps = Collections .emptyList(); private static final List<MediaType> MediaTypeAllList = Collections .singletonList(MediaType.ALL); private MediaTypesHandler mediaTypesHandler = new MediaTypesHandler() { @Override public List<MediaType> getMediaTypes(HttpServletRequest request) { return emptyEmdiaTyps; } }; public boolean isWebVaild() { return webVaild; } public void setWebVaild(boolean webVaild) { this.webVaild = webVaild; } public void setMediaTypesHandler(MediaTypesHandler mediaTypesHandler) { this.mediaTypesHandler = mediaTypesHandler; } private ObjectFactory objectFactory; @SuppressWarnings("rawtypes") public ExtendableAnnotationMethodHandlerAdapter() { // no restriction of HTTP methods by default super(false); // See SPR-7316 StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); stringHttpMessageConverter.setWriteAcceptCharset(false); this.messageConverters = new HttpMessageConverter[] { new ByteArrayHttpMessageConverter(), stringHttpMessageConverter, new SourceHttpMessageConverter(), new XmlAwareFormHttpMessageConverter() }; } public void setObjectFactory(ObjectFactory objectFactory) { this.objectFactory = objectFactory; } public void setBeanFactory(ConfigurableBeanFactory beanFactory) { this.beanFactory = beanFactory; } /** * Set if URL lookup should always use the full path within the current * servlet context. Else, the path within the current servlet mapping is * used if applicable (that is, in the case of a ".../*" servlet mapping in * web.xml). * <p> * Default is "false". * * @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath */ public void setAlwaysUseFullPath(boolean alwaysUseFullPath) { this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath); } /** * Set if context path and request URI should be URL-decoded. Both are * returned <i>undecoded</i> by the Servlet API, in contrast to the servlet * path. * <p> * Uses either the request encoding or the default encoding according to the * Servlet spec (ISO-8859-1). * * @see org.springframework.web.util.UrlPathHelper#setUrlDecode */ public void setUrlDecode(boolean urlDecode) { this.urlPathHelper.setUrlDecode(urlDecode); } /** * Set the UrlPathHelper to use for resolution of lookup paths. * <p> * Use this to override the default UrlPathHelper with a custom subclass, or * to share common UrlPathHelper settings across multiple HandlerMappings * and HandlerAdapters. */ public void setUrlPathHelper(UrlPathHelper urlPathHelper) { Assert.notNull(urlPathHelper, "UrlPathHelper must not be null"); this.urlPathHelper = urlPathHelper; } /** * Set the PathMatcher implementation to use for matching URL paths against * registered URL patterns. * <p> * Default is {@link org.springframework.util.AntPathMatcher}. */ public void setPathMatcher(PathMatcher pathMatcher) { Assert.notNull(pathMatcher, "PathMatcher must not be null"); this.pathMatcher = pathMatcher; } /** * Set the MethodNameResolver to use for resolving default handler methods * (carrying an empty <code>@RequestMapping</code> annotation). * <p> * Will only kick in when the handler method cannot be resolved uniquely * through the annotation metadata already. */ public void setMethodNameResolver(MethodNameResolver methodNameResolver) { this.methodNameResolver = methodNameResolver; } /** * Specify a WebBindingInitializer which will apply pre-configured * configuration to every DataBinder that this controller uses. */ public void setWebBindingInitializer( WebBindingInitializer webBindingInitializer) { this.webBindingInitializer = webBindingInitializer; } /** * Specify the strategy to store session attributes with. * <p> * Default is * {@link org.springframework.web.bind.support.DefaultSessionAttributeStore} * , storing session attributes in the HttpSession, using the same attribute * name as in the model. */ public void setSessionAttributeStore( SessionAttributeStore sessionAttributeStore) { Assert.notNull(sessionAttributeStore, "SessionAttributeStore must not be null"); this.sessionAttributeStore = sessionAttributeStore; } /** * Cache content produced by <code>@SessionAttributes</code> annotated * handlers for the given number of seconds. Default is 0, preventing * caching completely. * <p> * In contrast to the "cacheSeconds" property which will apply to all * general handlers (but not to <code>@SessionAttributes</code> annotated * handlers), this setting will apply to <code>@SessionAttributes</code> * annotated handlers only. * * @see #setCacheSeconds * @see org.springframework.web.bind.annotation.SessionAttributes */ public void setCacheSecondsForSessionAttributeHandlers( int cacheSecondsForSessionAttributeHandlers) { this.cacheSecondsForSessionAttributeHandlers = cacheSecondsForSessionAttributeHandlers; } /** * Set if controller execution should be synchronized on the session, to * serialize parallel invocations from the same client. * <p> * More specifically, the execution of the * <code>handleRequestInternal</code> method will get synchronized if this * flag is "true". The best available session mutex will be used for the * synchronization; ideally, this will be a mutex exposed by * HttpSessionMutexListener. * <p> * The session mutex is guaranteed to be the same object during the entire * lifetime of the session, available under the key defined by the * <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a safe * reference to synchronize on for locking on the current session. * <p> * In many cases, the HttpSession reference itself is a safe mutex as well, * since it will always be the same object reference for the same active * logical session. However, this is not guaranteed across different servlet * containers; the only 100% safe way is a session mutex. * * @see org.springframework.web.util.HttpSessionMutexListener * @see org.springframework.web.util.WebUtils#getSessionMutex(javax.servlet.http.HttpSession) */ public void setSynchronizeOnSession(boolean synchronizeOnSession) { this.synchronizeOnSession = synchronizeOnSession; } /** * Set the ParameterNameDiscoverer to use for resolving method parameter * names if needed (e.g. for default attribute names). * <p> * Default is a * {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}. */ public void setParameterNameDiscoverer( ParameterNameDiscoverer parameterNameDiscoverer) { this.parameterNameDiscoverer = parameterNameDiscoverer; } /** * Set a custom WebArgumentResolvers to use for special method parameter * types. * <p> * Such a custom WebArgumentResolver will kick in first, having a chance to * resolve an argument value before the standard argument handling kicks in. */ public void setCustomArgumentResolver(WebArgumentResolver argumentResolver) { this.customArgumentResolvers = new WebArgumentResolver[] { argumentResolver }; } /** * Set one or more custom WebArgumentResolvers to use for special method * parameter types. * <p> * Any such custom WebArgumentResolver will kick in first, having a chance * to resolve an argument value before the standard argument handling kicks * in. */ public void setCustomArgumentResolvers( WebArgumentResolver[] argumentResolvers) { this.customArgumentResolvers = argumentResolvers; } /** * Set a custom ModelAndViewResolvers to use for special method return * types. * <p> * Such a custom ModelAndViewResolver will kick in first, having a chance to * resolve a return value before the standard ModelAndView handling kicks * in. */ public void setCustomModelAndViewResolver( ModelAndViewResolver customModelAndViewResolver) { this.customModelAndViewResolvers = new ModelAndViewResolver[] { customModelAndViewResolver }; } /** * Set one or more custom ModelAndViewResolvers to use for special method * return types. * <p> * Any such custom ModelAndViewResolver will kick in first, having a chance * to resolve a return value before the standard ModelAndView handling kicks * in. */ public void setCustomModelAndViewResolvers( ModelAndViewResolver[] customModelAndViewResolvers) { this.customModelAndViewResolvers = customModelAndViewResolvers; } /** * Set the message body converters to use. * <p> * These converters are used to convert from and to HTTP requests and * responses. */ public void setMessageConverters(HttpMessageConverter<?>[] messageConverters) { this.messageConverters = messageConverters; } /** * Return the message body converters that this adapter has been configured * with. */ public HttpMessageConverter<?>[] getMessageConverters() { return messageConverters; } /** * Specify the order value for this HandlerAdapter bean. * <p> * Default value is <code>Integer.MAX_VALUE</code>, meaning that it's * non-ordered. * * @see org.springframework.core.Ordered#getOrder() */ public void setOrder(int order) { this.order = order; } public int getOrder() { return this.order; } public void setBeanFactory(BeanFactory beanFactory) { if (beanFactory instanceof ConfigurableBeanFactory) { this.beanFactory = (ConfigurableBeanFactory) beanFactory; this.expressionContext = new BeanExpressionContext( this.beanFactory, new RequestScope()); } } public void setMethodInvokeInterceptors( List<AnnotationMethodHandlerInterceptor> methodInvokeInterceptors) { this.methodInvokeInterceptors = methodInvokeInterceptors; } public boolean supports(Object handler) { return getMethodResolver(handler).hasHandlerMethods(); } public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (AnnotationUtils.findAnnotation(handler.getClass(), SessionAttributes.class) != null) { // Always prevent caching in case of session attribute management. checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true); // Prepare cached set of session attributes names. } else { // Uses configured default cacheSeconds setting. checkAndPrepare(request, response, true); } // Execute invokeHandlerMethod in synchronized block if required. if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { return invokeHandlerMethod(request, response, handler); } } } return invokeHandlerMethod(request, response, handler); } protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ServletHandlerMethodResolver methodResolver = getMethodResolver(handler); Method handlerMethod = methodResolver.resolveHandlerMethod(request); ServletHandlerMethodInvoker methodInvoker = new ServletHandlerMethodInvoker( methodResolver); ServletWebRequest webRequest = new ServletWebRequest(request, response); ExtendedModelMap implicitModel = new BindingAwareModelMap(); if (this.methodInvokeInterceptors != null && !this.methodInvokeInterceptors.isEmpty()) { for (AnnotationMethodHandlerInterceptor interceptor : this.methodInvokeInterceptors) { interceptor.preInvoke(handlerMethod, handler, webRequest); } } Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel); ModelAndView mav = methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel, webRequest); if (this.methodInvokeInterceptors != null && !this.methodInvokeInterceptors.isEmpty()) { for (int i = this.methodInvokeInterceptors.size() - 1; i >= 0; i--) { this.methodInvokeInterceptors.get(i).postInvoke(handlerMethod, handler, webRequest, mav); } } methodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest); return mav; } public long getLastModified(HttpServletRequest request, Object handler) { return -1; } /** * Build a HandlerMethodResolver for the given handler type. */ private ServletHandlerMethodResolver getMethodResolver(Object handler) { Class<?> handlerClass = ClassUtils.getUserClass(handler); synchronized (this.methodResolverCache) { ServletHandlerMethodResolver resolver = this.methodResolverCache .get(handlerClass); if (resolver == null) { resolver = new ServletHandlerMethodResolver(handlerClass); this.methodResolverCache.put(handlerClass, resolver); } return resolver; } } /** * Template method for creating a new ServletRequestDataBinder instance. * <p> * The default implementation creates a standard ServletRequestDataBinder. * This can be overridden for custom ServletRequestDataBinder subclasses. * * @param request * current HTTP request * @param target * the target object to bind onto (or <code>null</code> if the * binder is just used to convert a plain parameter value) * @param objectName * the objectName of the target object * @return the ServletRequestDataBinder instance to use * @throws Exception * in case of invalid state or arguments * @see ServletRequestDataBinder#bind(javax.servlet.ServletRequest) * @see ServletRequestDataBinder#convertIfNecessary(Object, Class, * org.springframework.core.MethodParameter) */ protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object target, String objectName) throws Exception { return new ServletRequestDataBinder(target, objectName); } /** * Template method for creating a new HttpInputMessage instance. * <p> * The default implementation creates a standard * {@link ServletServerHttpRequest}. This can be overridden for custom * {@code HttpInputMessage} implementations * * @param servletRequest * current HTTP request * @return the HttpInputMessage instance to use * @throws Exception * in case of errors */ protected HttpInputMessage createHttpInputMessage( HttpServletRequest servletRequest) throws Exception { return new ServletServerHttpRequest(servletRequest); } /** * Template method for creating a new HttpOuputMessage instance. * <p> * The default implementation creates a standard * {@link ServletServerHttpResponse}. This can be overridden for custom * {@code HttpOutputMessage} implementations * * @param servletResponse * current HTTP response * @return the HttpInputMessage instance to use * @throws Exception * in case of errors */ protected HttpOutputMessage createHttpOutputMessage( HttpServletResponse servletResponse) throws Exception { return new ServletServerHttpResponse(servletResponse); } /** * Servlet-specific subclass of {@link HandlerMethodResolver}. */ private class ServletHandlerMethodResolver extends HandlerMethodResolver { private final Map<Method, RequestMappingInfo> mappings = new HashMap<Method, RequestMappingInfo>(); private ServletHandlerMethodResolver(Class<?> handlerType) { init(handlerType); } @Override protected boolean isHandlerMethod(Method method) { if (this.mappings.containsKey(method)) { return true; } RequestMapping mapping = AnnotationUtils.findAnnotation(method, RequestMapping.class); if (mapping != null) { String[] patterns = mapping.value(); RequestMethod[] methods = new RequestMethod[0]; String[] params = new String[0]; String[] headers = new String[0]; if (!hasTypeLevelMapping() || !Arrays.equals(mapping.method(), getTypeLevelMapping().method())) { methods = mapping.method(); } if (!hasTypeLevelMapping() || !Arrays.equals(mapping.params(), getTypeLevelMapping().params())) { params = mapping.params(); } if (!hasTypeLevelMapping() || !Arrays.equals(mapping.headers(), getTypeLevelMapping().headers())) { headers = mapping.headers(); } RequestMappingInfo mappingInfo = new RequestMappingInfo( patterns, methods, params, headers); this.mappings.put(method, mappingInfo); return true; } return false; } public Method resolveHandlerMethod(HttpServletRequest request) throws ServletException { String lookupPath = urlPathHelper.getLookupPathForRequest(request); Comparator<String> pathComparator = pathMatcher .getPatternComparator(lookupPath); Map<RequestSpecificMappingInfo, Method> targetHandlerMethods = new LinkedHashMap<RequestSpecificMappingInfo, Method>(); Set<String> allowedMethods = new LinkedHashSet<String>(7); String resolvedMethodName = null; for (Method handlerMethod : getHandlerMethods()) { RequestSpecificMappingInfo mappingInfo = new RequestSpecificMappingInfo( this.mappings.get(handlerMethod)); boolean match = false; if (mappingInfo.hasPatterns()) { for (String pattern : mappingInfo.getPatterns()) { if (!hasTypeLevelMapping() && !pattern.startsWith("/")) { pattern = "/" + pattern; } String combinedPattern = getCombinedPattern(pattern, lookupPath, request); if (combinedPattern != null) { if (mappingInfo.matches(request)) { match = true; mappingInfo.addMatchedPattern(combinedPattern); } else { if (!mappingInfo.matchesRequestMethod(request)) { allowedMethods.addAll(mappingInfo .methodNames()); } break; } } } mappingInfo.sortMatchedPatterns(pathComparator); } else { // No paths specified: parameter match sufficient. match = mappingInfo.matches(request); if (match && mappingInfo.getMethodCount() == 0 && mappingInfo.getParamCount() == 0 && resolvedMethodName != null && !resolvedMethodName.equals(handlerMethod .getName())) { match = false; } else { if (!mappingInfo.matchesRequestMethod(request)) { allowedMethods.addAll(mappingInfo.methodNames()); } } } if (match) { Method oldMappedMethod = targetHandlerMethods.put( mappingInfo, handlerMethod); if (oldMappedMethod != null && oldMappedMethod != handlerMethod) { if (methodNameResolver != null && !mappingInfo.hasPatterns()) { if (!oldMappedMethod.getName().equals( handlerMethod.getName())) { if (resolvedMethodName == null) { resolvedMethodName = methodNameResolver .getHandlerMethodName(request); } if (!resolvedMethodName.equals(oldMappedMethod .getName())) { oldMappedMethod = null; } if (!resolvedMethodName.equals(handlerMethod .getName())) { if (oldMappedMethod != null) { targetHandlerMethods.put(mappingInfo, oldMappedMethod); oldMappedMethod = null; } else { targetHandlerMethods .remove(mappingInfo); } } } } if (oldMappedMethod != null) { throw new IllegalStateException( "Ambiguous handler methods mapped for HTTP path '" + lookupPath + "': {" + oldMappedMethod + ", " + handlerMethod + "}. If you intend to handle the same path in multiple methods, then factor " + "them out into a dedicated handler class with that path mapped at the type level!"); } } } } if (!targetHandlerMethods.isEmpty()) { List<RequestSpecificMappingInfo> matches = new ArrayList<RequestSpecificMappingInfo>( targetHandlerMethods.keySet()); RequestSpecificMappingInfoComparator requestMappingInfoComparator = new RequestSpecificMappingInfoComparator( pathComparator, request); Collections.sort(matches, requestMappingInfoComparator); RequestSpecificMappingInfo bestMappingMatch = matches.get(0); String bestMatchedPath = bestMappingMatch.bestMatchedPattern(); if (bestMatchedPath != null) { extractHandlerMethodUriTemplates(bestMatchedPath, lookupPath, request); } return targetHandlerMethods.get(bestMappingMatch); } else { if (!allowedMethods.isEmpty()) { throw new HttpRequestMethodNotSupportedException( request.getMethod(), StringUtils.toStringArray(allowedMethods)); } throw new NoSuchRequestHandlingMethodException(lookupPath, request.getMethod(), request.getParameterMap()); } } /** * Determines the combined pattern for the given methodLevelPattern and * path. * <p> * Uses the following algorithm: * <ol> * <li>If there is a type-level mapping with path information, it is * {@linkplain PathMatcher#combine(String, String) combined} with the * method-level pattern.</li> * <li>If there is a * {@linkplain HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE best * matching pattern} in the request, it is combined with the * method-level pattern.</li> * <li>Otherwise, the method-level pattern is returned.</li> * </ol> */ private String getCombinedPattern(String methodLevelPattern, String lookupPath, HttpServletRequest request) { if (hasTypeLevelMapping() && (!ObjectUtils.isEmpty(getTypeLevelMapping().value()))) { String[] typeLevelPatterns = getTypeLevelMapping().value(); for (String typeLevelPattern : typeLevelPatterns) { if (!typeLevelPattern.startsWith("/")) { typeLevelPattern = "/" + typeLevelPattern; } String combinedPattern = pathMatcher.combine( typeLevelPattern, methodLevelPattern); if (isPathMatchInternal(combinedPattern, lookupPath)) { return combinedPattern; } } return null; } String bestMatchingPattern = (String) request .getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); if (StringUtils.hasText(bestMatchingPattern) && bestMatchingPattern.endsWith("*")) { String combinedPattern = pathMatcher.combine( bestMatchingPattern, methodLevelPattern); if (!combinedPattern.equals(bestMatchingPattern) && (isPathMatchInternal(combinedPattern, lookupPath))) { return combinedPattern; } } if (isPathMatchInternal(methodLevelPattern, lookupPath)) { return methodLevelPattern; } return null; } private boolean isPathMatchInternal(String pattern, String lookupPath) { if (pattern.equals(lookupPath) || pathMatcher.match(pattern, lookupPath)) { return true; } boolean hasSuffix = pattern.indexOf('.') != -1; if (!hasSuffix && pathMatcher.match(pattern + ".*", lookupPath)) { return true; } boolean endsWithSlash = pattern.endsWith("/"); if (!endsWithSlash && pathMatcher.match(pattern + "/", lookupPath)) { return true; } return false; } @SuppressWarnings("unchecked") private void extractHandlerMethodUriTemplates(String mappedPattern, String lookupPath, HttpServletRequest request) { Map<String, String> variables = (Map<String, String>) request .getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); int patternVariableCount = StringUtils.countOccurrencesOf( mappedPattern, "{"); if ((variables == null || patternVariableCount != variables.size()) && pathMatcher.match(mappedPattern, lookupPath)) { variables = pathMatcher.extractUriTemplateVariables( mappedPattern, lookupPath); request.setAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, variables); } } } /** * Servlet-specific subclass of {@link HandlerMethodInvoker}. */ private class ServletHandlerMethodInvoker extends HandlerMethodInvoker { private boolean responseArgumentUsed = false; private ServletHandlerMethodInvoker(HandlerMethodResolver resolver) { super(resolver, webBindingInitializer, sessionAttributeStore, parameterNameDiscoverer, customArgumentResolvers, messageConverters); } @Override protected void raiseMissingParameterException(String paramName, @SuppressWarnings("rawtypes") Class paramType) throws Exception { throw new MissingServletRequestParameterException(paramName, paramType.getSimpleName()); } @Override protected void raiseSessionRequiredException(String message) throws Exception { throw new HttpSessionRequiredException(message); } @Override protected WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception { return ExtendableAnnotationMethodHandlerAdapter.this.createBinder( webRequest.getNativeRequest(HttpServletRequest.class), target, objectName); } @Override protected void doBind(WebDataBinder binder, NativeWebRequest webRequest) throws Exception { ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder; servletBinder.bind(webRequest .getNativeRequest(ServletRequest.class)); } @Override protected HttpInputMessage createHttpInputMessage( NativeWebRequest webRequest) throws Exception { HttpServletRequest servletRequest = webRequest .getNativeRequest(HttpServletRequest.class); return ExtendableAnnotationMethodHandlerAdapter.this .createHttpInputMessage(servletRequest); } @Override protected HttpOutputMessage createHttpOutputMessage( NativeWebRequest webRequest) throws Exception { HttpServletResponse servletResponse = (HttpServletResponse) webRequest .getNativeResponse(); return ExtendableAnnotationMethodHandlerAdapter.this .createHttpOutputMessage(servletResponse); } @Override protected Object resolveDefaultValue(String value) { if (beanFactory == null) { return value; } String placeholdersResolved = beanFactory .resolveEmbeddedValue(value); BeanExpressionResolver exprResolver = beanFactory .getBeanExpressionResolver(); if (exprResolver == null) { return value; } return exprResolver.evaluate(placeholdersResolved, expressionContext); } @Override protected Object resolveCookieValue(String cookieName, @SuppressWarnings("rawtypes") Class paramType, NativeWebRequest webRequest) throws Exception { HttpServletRequest servletRequest = webRequest .getNativeRequest(HttpServletRequest.class); Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName); if (Cookie.class.isAssignableFrom(paramType)) { return cookieValue; } else if (cookieValue != null) { return urlPathHelper.decodeRequestString(servletRequest, cookieValue.getValue()); } else { return null; } } @Override @SuppressWarnings({ "unchecked" }) protected String resolvePathVariable(String pathVarName, @SuppressWarnings("rawtypes") Class paramType, NativeWebRequest webRequest) throws Exception { HttpServletRequest servletRequest = webRequest .getNativeRequest(HttpServletRequest.class); Map<String, String> uriTemplateVariables = (Map<String, String>) servletRequest .getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); if (uriTemplateVariables == null || !uriTemplateVariables.containsKey(pathVarName)) { throw new IllegalStateException( "Could not find @PathVariable [" + pathVarName + "] in @RequestMapping"); } return uriTemplateVariables.get(pathVarName); } @Override protected Object resolveStandardArgument(Class<?> parameterType, NativeWebRequest webRequest) throws Exception { HttpServletRequest request = webRequest .getNativeRequest(HttpServletRequest.class); HttpServletResponse response = webRequest .getNativeResponse(HttpServletResponse.class); if (ServletRequest.class.isAssignableFrom(parameterType) || MultipartRequest.class.isAssignableFrom(parameterType)) { Object nativeRequest = webRequest .getNativeRequest(parameterType); if (nativeRequest == null) { throw new IllegalStateException( "Current request is not of type [" + parameterType.getName() + "]: " + request); } return nativeRequest; } else if (ServletResponse.class.isAssignableFrom(parameterType)) { this.responseArgumentUsed = true; Object nativeResponse = webRequest .getNativeResponse(parameterType); if (nativeResponse == null) { throw new IllegalStateException( "Current response is not of type [" + parameterType.getName() + "]: " + response); } return nativeResponse; } else if (HttpSession.class.isAssignableFrom(parameterType)) { return request.getSession(); } else if (Principal.class.isAssignableFrom(parameterType)) { return request.getUserPrincipal(); } else if (Locale.class.equals(parameterType)) { return RequestContextUtils.getLocale(request); } else if (InputStream.class.isAssignableFrom(parameterType)) { return request.getInputStream(); } else if (Reader.class.isAssignableFrom(parameterType)) { return request.getReader(); } else if (OutputStream.class.isAssignableFrom(parameterType)) { this.responseArgumentUsed = true; return response.getOutputStream(); } else if (Writer.class.isAssignableFrom(parameterType)) { this.responseArgumentUsed = true; return response.getWriter(); } return super.resolveStandardArgument(parameterType, webRequest); } @SuppressWarnings("unchecked") public ModelAndView getModelAndView(Method handlerMethod, Class<?> handlerType, Object returnValue, ExtendedModelMap implicitModel, ServletWebRequest webRequest) throws Exception { ResponseStatus responseStatusAnn = AnnotationUtils.findAnnotation( handlerMethod, ResponseStatus.class); if (responseStatusAnn != null) { HttpStatus responseStatus = responseStatusAnn.value(); String reason = responseStatusAnn.reason(); if (!StringUtils.hasText(reason)) { webRequest.getResponse().setStatus(responseStatus.value()); } else { webRequest.getResponse().sendError(responseStatus.value(), reason); } // to be picked up by the RedirectView webRequest.getRequest().setAttribute( View.RESPONSE_STATUS_ATTRIBUTE, responseStatus); responseArgumentUsed = true; } // jresplus // invoke retrun value if (customReturnValueHandlerResolver != null) { returnValue = customReturnValueHandlerResolver .resolveReturnValue(handlerMethod, handlerType, returnValue, implicitModel, webRequest); } // jresplus // Invoke custom resolvers if present... if (customModelAndViewResolvers != null) { for (ModelAndViewResolver mavResolver : customModelAndViewResolvers) { ModelAndView mav = mavResolver.resolveModelAndView( handlerMethod, handlerType, returnValue, implicitModel, webRequest); if (mav != ModelAndViewResolver.UNRESOLVED) { return mav; } } } if (returnValue instanceof HttpEntity) { handleHttpEntityResponse((HttpEntity<?>) returnValue, webRequest); return null; } else if (AnnotationUtils.findAnnotation(handlerMethod, ResponseBody.class) != null) { handleResponseBody(returnValue, webRequest); return null; } else if (returnValue instanceof ModelAndView) { ModelAndView mav = (ModelAndView) returnValue; mav.getModelMap().mergeAttributes(implicitModel); return mav; } else if (returnValue instanceof Model) { return new ModelAndView().addAllObjects(implicitModel) .addAllObjects(((Model) returnValue).asMap()); } else if (returnValue instanceof View) { return new ModelAndView((View) returnValue) .addAllObjects(implicitModel); } else if (AnnotationUtils.findAnnotation(handlerMethod, ModelAttribute.class) != null) { addReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel); return new ModelAndView().addAllObjects(implicitModel); } else if (returnValue instanceof Map) { return new ModelAndView().addAllObjects(implicitModel) .addAllObjects((Map<String, ?>) returnValue); } else if (returnValue instanceof String) { return new ModelAndView((String) returnValue) .addAllObjects(implicitModel); } else if (returnValue == null) { // Either returned null or was 'void' return. if (this.responseArgumentUsed || webRequest.isNotModified()) { return null; } else { // Assuming view name translation... return new ModelAndView().addAllObjects(implicitModel); } } else if (!BeanUtils.isSimpleProperty(returnValue.getClass())) { // Assume a single model attribute... addReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel); return new ModelAndView().addAllObjects(implicitModel); } else { throw new IllegalArgumentException( "Invalid handler method return value: " + returnValue); } } private void handleResponseBody(Object returnValue, ServletWebRequest webRequest) throws Exception { if (returnValue == null) { return; } HttpInputMessage inputMessage = createHttpInputMessage(webRequest); HttpOutputMessage outputMessage = createHttpOutputMessage(webRequest); writeWithMessageConverters(returnValue, inputMessage, outputMessage, webRequest); } private void handleHttpEntityResponse(HttpEntity<?> responseEntity, ServletWebRequest webRequest) throws Exception { if (responseEntity == null) { return; } HttpInputMessage inputMessage = createHttpInputMessage(webRequest); HttpOutputMessage outputMessage = createHttpOutputMessage(webRequest); if (responseEntity instanceof ResponseEntity && outputMessage instanceof ServerHttpResponse) { ((ServerHttpResponse) outputMessage) .setStatusCode(((ResponseEntity<?>) responseEntity) .getStatusCode()); } HttpHeaders entityHeaders = responseEntity.getHeaders(); if (!entityHeaders.isEmpty()) { outputMessage.getHeaders().putAll(entityHeaders); } Object body = responseEntity.getBody(); if (body != null) { writeWithMessageConverters(body, inputMessage, outputMessage, webRequest); } else { // flush headers outputMessage.getBody(); } } @SuppressWarnings("unchecked") private void writeWithMessageConverters(Object returnValue, HttpInputMessage inputMessage, HttpOutputMessage outputMessage, ServletWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException { List<MediaType> acceptedMediaTypes = mediaTypesHandler .getMediaTypes(webRequest.getRequest()); if (acceptedMediaTypes.isEmpty()) { acceptedMediaTypes = MediaTypeAllList; } else { MediaType.sortByQualityValue(acceptedMediaTypes); } MediaType.sortByQualityValue(acceptedMediaTypes); Class<?> returnValueType = returnValue.getClass(); List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>(); if (getMessageConverters() != null) { for (MediaType acceptedMediaType : acceptedMediaTypes) { for (@SuppressWarnings("rawtypes") HttpMessageConverter messageConverter : getMessageConverters()) { if (messageConverter.canWrite(returnValueType, acceptedMediaType)) { messageConverter.write(returnValue, acceptedMediaType, outputMessage); if (logger.isDebugEnabled()) { MediaType contentType = outputMessage .getHeaders().getContentType(); if (contentType == null) { contentType = acceptedMediaType; } logger.debug("Written [" + returnValue + "] as \"" + contentType + "\" using [" + messageConverter + "]"); } this.responseArgumentUsed = true; return; } } } for (@SuppressWarnings("rawtypes") HttpMessageConverter messageConverter : messageConverters) { allSupportedMediaTypes.addAll(messageConverter .getSupportedMediaTypes()); } } throw new HttpMediaTypeNotAcceptableException( allSupportedMediaTypes); } } /** * Holder for request mapping metadata. */ static class RequestMappingInfo { private final String[] patterns; private final RequestMethod[] methods; private final String[] params; private final String[] headers; RequestMappingInfo(String[] patterns, RequestMethod[] methods, String[] params, String[] headers) { this.patterns = patterns != null ? patterns : new String[0]; this.methods = methods != null ? methods : new RequestMethod[0]; this.params = params != null ? params : new String[0]; this.headers = headers != null ? headers : new String[0]; } public boolean hasPatterns() { return patterns.length > 0; } public String[] getPatterns() { return patterns; } public int getMethodCount() { return methods.length; } public int getParamCount() { return params.length; } public int getHeaderCount() { return headers.length; } public boolean matches(HttpServletRequest request) { return matchesRequestMethod(request) && matchesParameters(request) && matchesHeaders(request); } public boolean matchesHeaders(HttpServletRequest request) { return ServletAnnotationMappingUtils.checkHeaders(this.headers, request); } public boolean matchesParameters(HttpServletRequest request) { return ServletAnnotationMappingUtils.checkParameters(this.params, request); } public boolean matchesRequestMethod(HttpServletRequest request) { return ServletAnnotationMappingUtils.checkRequestMethod( this.methods, request); } public Set<String> methodNames() { Set<String> methodNames = new LinkedHashSet<String>(methods.length); for (RequestMethod method : methods) { methodNames.add(method.name()); } return methodNames; } @Override public boolean equals(Object obj) { RequestMappingInfo other = (RequestMappingInfo) obj; return (Arrays.equals(this.patterns, other.patterns) && Arrays.equals(this.methods, other.methods) && Arrays.equals(this.params, other.params) && Arrays .equals(this.headers, other.headers)); } @Override public int hashCode() { return (Arrays.hashCode(this.patterns) * 23 + Arrays.hashCode(this.methods) * 29 + Arrays.hashCode(this.params) * 31 + Arrays .hashCode(this.headers)); } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(Arrays.asList(patterns)); if (methods.length > 0) { builder.append(','); builder.append(Arrays.asList(methods)); } if (headers.length > 0) { builder.append(','); builder.append(Arrays.asList(headers)); } if (params.length > 0) { builder.append(','); builder.append(Arrays.asList(params)); } return builder.toString(); } } /** * Subclass of {@link RequestMappingInfo} that holds request-specific data. */ static class RequestSpecificMappingInfo extends RequestMappingInfo { private final List<String> matchedPatterns = new ArrayList<String>(); RequestSpecificMappingInfo(String[] patterns, RequestMethod[] methods, String[] params, String[] headers) { super(patterns, methods, params, headers); } RequestSpecificMappingInfo(RequestMappingInfo other) { super(other.patterns, other.methods, other.params, other.headers); } public void addMatchedPattern(String matchedPattern) { matchedPatterns.add(matchedPattern); } public void sortMatchedPatterns(Comparator<String> pathComparator) { Collections.sort(matchedPatterns, pathComparator); } public String bestMatchedPattern() { return (!this.matchedPatterns.isEmpty() ? this.matchedPatterns .get(0) : null); } } /** * Comparator capable of sorting {@link RequestSpecificMappingInfo}s (RHIs) * so that sorting a list with this comparator will result in: * <ul> * <li>RHIs with * {@linkplain AnnotationMethodHandlerAdapter.RequestSpecificMappingInfo#matchedPatterns * better matched paths} take prescedence over those with a weaker match (as * expressed by the {@linkplain PathMatcher#getPatternComparator(String) * path pattern comparator}.) Typically, this means that patterns without * wild cards and uri templates will be ordered before those without.</li> * <li>RHIs with one single {@linkplain RequestMappingInfo#methods request * method} will be ordered before those without a method, or with more than * one method.</li> * <li>RHIs with more {@linkplain RequestMappingInfo#params request * parameters} will be ordered before those with less parameters</li> </ol> */ static class RequestSpecificMappingInfoComparator implements Comparator<RequestSpecificMappingInfo> { private final Comparator<String> pathComparator; private final ServerHttpRequest request; RequestSpecificMappingInfoComparator(Comparator<String> pathComparator, HttpServletRequest request) { this.pathComparator = pathComparator; this.request = new ServletServerHttpRequest(request); } public int compare(RequestSpecificMappingInfo info1, RequestSpecificMappingInfo info2) { int pathComparison = pathComparator.compare( info1.bestMatchedPattern(), info2.bestMatchedPattern()); if (pathComparison != 0) { return pathComparison; } int info1ParamCount = info1.getParamCount(); int info2ParamCount = info2.getParamCount(); if (info1ParamCount != info2ParamCount) { return info2ParamCount - info1ParamCount; } int info1HeaderCount = info1.getHeaderCount(); int info2HeaderCount = info2.getHeaderCount(); if (info1HeaderCount != info2HeaderCount) { return info2HeaderCount - info1HeaderCount; } int acceptComparison = compareAcceptHeaders(info1, info2); if (acceptComparison != 0) { return acceptComparison; } int info1MethodCount = info1.getMethodCount(); int info2MethodCount = info2.getMethodCount(); if (info1MethodCount == 0 && info2MethodCount > 0) { return 1; } else if (info2MethodCount == 0 && info1MethodCount > 0) { return -1; } else if (info1MethodCount == 1 & info2MethodCount > 1) { return -1; } else if (info2MethodCount == 1 & info1MethodCount > 1) { return 1; } return 0; } private int compareAcceptHeaders(RequestMappingInfo info1, RequestMappingInfo info2) { List<MediaType> requestAccepts = request.getHeaders().getAccept(); MediaType.sortByQualityValue(requestAccepts); List<MediaType> info1Accepts = getAcceptHeaderValue(info1); List<MediaType> info2Accepts = getAcceptHeaderValue(info2); for (MediaType requestAccept : requestAccepts) { int pos1 = indexOfIncluded(info1Accepts, requestAccept); int pos2 = indexOfIncluded(info2Accepts, requestAccept); if (pos1 != pos2) { return pos2 - pos1; } } return 0; } private int indexOfIncluded(List<MediaType> infoAccepts, MediaType requestAccept) { for (int i = 0; i < infoAccepts.size(); i++) { MediaType info1Accept = infoAccepts.get(i); if (requestAccept.includes(info1Accept)) { return i; } } return -1; } private List<MediaType> getAcceptHeaderValue(RequestMappingInfo info) { for (String header : info.headers) { int separator = header.indexOf('='); if (separator != -1) { String key = header.substring(0, separator); String value = header.substring(separator + 1); if ("Accept".equalsIgnoreCase(key)) { return MediaType.parseMediaTypes(value); } } } return Collections.emptyList(); } } // jresplus public void afterPropertiesSet() throws Exception { if (this.isWebVaild() == false) { this.webBindingInitializer = null; } this.customReturnValueHandlerResolver = objectFactory .getBean(ReturnValueHandlerResolver.class); /** * ���ڼ����û������customArgumentResolvers */ WebArgumentResolver[] webArgumentResolvers = objectFactory .getBeansOfType4Array(WebArgumentResolver.class); if (webArgumentResolvers != null) { if (ArrayUtil.isEmpty(this.customArgumentResolvers)) { this.customArgumentResolvers = webArgumentResolvers; } else { WebArgumentResolver[] webArgumentResolverArray = this.customArgumentResolvers; this.customArgumentResolvers = new WebArgumentResolver[this.customArgumentResolvers.length + webArgumentResolvers.length]; System.arraycopy(webArgumentResolverArray, 0, this.customArgumentResolvers, 0, webArgumentResolverArray.length); int length = webArgumentResolverArray.length; webArgumentResolverArray = webArgumentResolvers; System.arraycopy(webArgumentResolverArray, 0, this.customArgumentResolvers, length - 1, webArgumentResolverArray.length); } } this.methodInvokeInterceptors = objectFactory .getBeansOfType4List(AnnotationMethodHandlerInterceptor.class); ModelAndViewResolver[] modelAndViewResolvers = objectFactory .getBeansOfType4Array(ModelAndViewResolver.class); if (modelAndViewResolvers != null) { if (ArrayUtil.isEmpty(this.customModelAndViewResolvers)) { this.customModelAndViewResolvers = modelAndViewResolvers; } else { ModelAndViewResolver[] modelAndViewResolverArray = this.customModelAndViewResolvers; this.customModelAndViewResolvers = new ModelAndViewResolver[this.customModelAndViewResolvers.length + modelAndViewResolvers.length]; System.arraycopy(modelAndViewResolverArray, 0, this.customModelAndViewResolvers, 0, modelAndViewResolverArray.length); int length = modelAndViewResolverArray.length; modelAndViewResolverArray = modelAndViewResolvers; System.arraycopy(modelAndViewResolverArray, 0, this.customModelAndViewResolvers, length - 1, modelAndViewResolverArray.length); } } } }