Index: lams_admin/web/WEB-INF/web.xml
===================================================================
diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243
--- lams_admin/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb)
+++ lams_admin/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243)
@@ -101,7 +101,7 @@
- org.springframework.web.context.ContextLoaderListener
+ org.lamsfoundation.lams.web.filter.LamsContextLoaderListener
Index: lams_central/web/WEB-INF/web.xml
===================================================================
diff -u -rbf7188dd95898df53f786a38b116214d005c8fb2 -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243
--- lams_central/web/WEB-INF/web.xml (.../web.xml) (revision bf7188dd95898df53f786a38b116214d005c8fb2)
+++ lams_central/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243)
@@ -167,7 +167,7 @@
- org.springframework.web.context.ContextLoaderListener
+ org.lamsfoundation.lams.web.filter.LamsContextLoaderListener
Index: lams_common/src/java/org/lamsfoundation/lams/beanRefContext.xml
===================================================================
diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243
--- lams_common/src/java/org/lamsfoundation/lams/beanRefContext.xml (.../beanRefContext.xml) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80)
+++ lams_common/src/java/org/lamsfoundation/lams/beanRefContext.xml (.../beanRefContext.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243)
@@ -3,36 +3,15 @@
-
Provides infrastructure for mapping handlers to URLs and configurable
+ * URL lookup. For information on the latter, see the
+ * {@link #setAlwaysUseFullPath} "alwaysUseFullPath"}
+ * and {@link #setUrlDecode "urlDecode"} properties.
+ *
+ * @author Juergen Hoeller
+ * @since 14.01.2004
+ * @deprecated as of 4.3, in favor of annotation-driven handler methods
+ */
+@Deprecated
+public abstract class AbstractUrlMethodNameResolver implements MethodNameResolver {
+
+ /** Logger available to subclasses */
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ private UrlPathHelper urlPathHelper = new UrlPathHelper();
+
+
+ /**
+ * Set if URL lookup should always use full path within current servlet
+ * context. Else, the path within the current servlet mapping is used
+ * if applicable (i.e. in the case of a ".../*" servlet mapping in web.xml).
+ * 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 undecoded by the Servlet API,
+ * in contrast to the servlet path.
+ *
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.
+ *
Use this to override the default UrlPathHelper with a custom subclass,
+ * or to share common UrlPathHelper settings across multiple MethodNameResolvers
+ * and HandlerMappings.
+ * @see org.springframework.web.servlet.handler.AbstractUrlHandlerMapping#setUrlPathHelper
+ */
+ public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
+ Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
+ this.urlPathHelper = urlPathHelper;
+ }
+
+
+ /**
+ * Retrieves the URL path to use for lookup and delegates to
+ * {@code getHandlerMethodNameForUrlPath}.
+ * Converts {@code null} values to NoSuchRequestHandlingMethodExceptions.
+ * @see #getHandlerMethodNameForUrlPath
+ */
+ @Override
+ public final String getHandlerMethodName(HttpServletRequest request)
+ throws NoSuchRequestHandlingMethodException {
+
+ String urlPath = this.urlPathHelper.getLookupPathForRequest(request);
+ String name = getHandlerMethodNameForUrlPath(urlPath);
+ if (name == null) {
+ throw new NoSuchRequestHandlingMethodException(urlPath, request.getMethod(), request.getParameterMap());
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("Returning handler method name '" + name + "' for lookup path: " + urlPath);
+ }
+ return name;
+ }
+
+ /**
+ * Return a method name that can handle this request, based on the
+ * given lookup path. Called by {@code getHandlerMethodName}.
+ * @param urlPath the URL path to use for lookup,
+ * according to the settings in this class
+ * @return a method name that can handle this request.
+ * Should return null if no matching method found.
+ * @see #getHandlerMethodName
+ * @see #setAlwaysUseFullPath
+ * @see #setUrlDecode
+ */
+ protected abstract String getHandlerMethodNameForUrlPath(String urlPath);
+
+}
Index: lams_common/src/java/org/lamsfoundation/lams/context/AnnotationMethodHandlerAdapter.java
===================================================================
diff -u
--- lams_common/src/java/org/lamsfoundation/lams/context/AnnotationMethodHandlerAdapter.java (revision 0)
+++ lams_common/src/java/org/lamsfoundation/lams/context/AnnotationMethodHandlerAdapter.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243)
@@ -0,0 +1,1297 @@
+/*
+ * Copyright 2002-2015 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 org.lamsfoundation.lams.context;
+
+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 java.util.concurrent.ConcurrentHashMap;
+
+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.config.BeanExpressionContext;
+import org.springframework.beans.factory.config.BeanExpressionResolver;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.Ordered;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+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.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.InitBinder;
+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.RequestParam;
+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.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.ModelAndViewResolver;
+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;
+
+/**
+ * Implementation of the {@link org.springframework.web.servlet.HandlerAdapter} interface
+ * that maps handler methods based on HTTP paths, HTTP methods, and request parameters
+ * expressed through the {@link RequestMapping} annotation.
+ *
+ *
+ * Supports request parameter binding through the {@link RequestParam} annotation.
+ * Also supports the {@link ModelAttribute} annotation for exposing model attribute
+ * values to the view, as well as {@link InitBinder} for binder initialization methods
+ * and {@link SessionAttributes} for automatic session management of specific attributes.
+ *
+ *
+ * This adapter can be customized through various bean properties.
+ * A common use case is to apply shared binder initialization logic through
+ * a custom {@link #setWebBindingInitializer WebBindingInitializer}.
+ *
+ * @author Juergen Hoeller
+ * @author Arjen Poutsma
+ * @author Sam Brannen
+ * @since 2.5
+ * @see #setPathMatcher
+ * @see #setMethodNameResolver
+ * @see #setWebBindingInitializer
+ * @see #setSessionAttributeStore
+ * @deprecated as of Spring 3.2, in favor of
+ * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
+ * RequestMappingHandlerAdapter}
+ */
+@Deprecated
+public class AnnotationMethodHandlerAdapter extends WebContentGenerator
+ implements HandlerAdapter, Ordered, BeanFactoryAware {
+
+ /**
+ * 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 WebBindingInitializer webBindingInitializer;
+
+ private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();
+
+ private int cacheSecondsForSessionAttributeHandlers = 0;
+
+ private boolean synchronizeOnSession = false;
+
+ private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
+
+ private WebArgumentResolver[] customArgumentResolvers;
+
+ private ModelAndViewResolver[] customModelAndViewResolvers;
+
+ private HttpMessageConverter>[] messageConverters;
+
+ private int order = Ordered.LOWEST_PRECEDENCE;
+
+ private ConfigurableBeanFactory beanFactory;
+
+ private BeanExpressionContext expressionContext;
+
+ private final Map, ServletHandlerMethodResolver> methodResolverCache = new ConcurrentHashMap<>(64);
+
+ private final Map, Boolean> sessionAnnotatedClassesCache = new ConcurrentHashMap<>(64);
+
+ public AnnotationMethodHandlerAdapter() {
+ // 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() };
+ }
+
+ /**
+ * 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).
+ *
+ * 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
+ * undecoded by the Servlet API, in contrast to the servlet path.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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} annotation).
+ *
+ * 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.
+ *
+ * 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} annotated handlers
+ * for the given number of seconds. Default is 0, preventing caching completely.
+ *
+ * In contrast to the "cacheSeconds" property which will apply to all general handlers
+ * (but not to {@code @SessionAttributes} annotated handlers), this setting will
+ * apply to {@code @SessionAttributes} 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.
+ *
+ * More specifically, the execution of the {@code handleRequestInternal}
+ * 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.
+ *
+ * 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} constant. It serves as a
+ * safe reference to synchronize on for locking on the current session.
+ *
+ * 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).
+ *
+ * Default is a {@link org.springframework.core.DefaultParameterNameDiscoverer}.
+ */
+ public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
+ this.parameterNameDiscoverer = parameterNameDiscoverer;
+ }
+
+ /**
+ * Set a custom WebArgumentResolvers to use for special method parameter types.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Default value is {@code Integer.MAX_VALUE}, meaning that it's non-ordered.
+ *
+ * @see org.springframework.core.Ordered#getOrder()
+ */
+ public void setOrder(int order) {
+ this.order = order;
+ }
+
+ @Override
+ public int getOrder() {
+ return this.order;
+ }
+
+ @Override
+ public void setBeanFactory(BeanFactory beanFactory) {
+ if (beanFactory instanceof ConfigurableBeanFactory) {
+ this.beanFactory = (ConfigurableBeanFactory) beanFactory;
+ this.expressionContext = new BeanExpressionContext(this.beanFactory, new RequestScope());
+ }
+ }
+
+ @Override
+ public boolean supports(Object handler) {
+ return getMethodResolver(handler).hasHandlerMethods();
+ }
+
+ @Override
+ public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
+ throws Exception {
+
+ Class> clazz = ClassUtils.getUserClass(handler);
+ Boolean annotatedWithSessionAttributes = this.sessionAnnotatedClassesCache.get(clazz);
+ if (annotatedWithSessionAttributes == null) {
+ annotatedWithSessionAttributes = (AnnotationUtils.findAnnotation(clazz, SessionAttributes.class) != null);
+ this.sessionAnnotatedClassesCache.put(clazz, annotatedWithSessionAttributes);
+ }
+
+ if (annotatedWithSessionAttributes) {
+ checkAndPrepare(request, response, this.cacheSecondsForSessionAttributeHandlers, true);
+ } else {
+ 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();
+
+ Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
+ ModelAndView mav = methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel,
+ webRequest);
+ methodInvoker.updateModelAttributes(handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
+ return mav;
+ }
+
+ /**
+ * This method always returns -1 since an annotated controller can have many methods,
+ * each requiring separate lastModified calculations. Instead, an
+ * {@link RequestMapping}-annotated method can calculate the lastModified value, call
+ * {@link org.springframework.web.context.request.WebRequest#checkNotModified(long)}
+ * to check it, and return {@code null} if that returns {@code true}.
+ *
+ * @see org.springframework.web.context.request.WebRequest#checkNotModified(long)
+ */
+ @Override
+ 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);
+ ServletHandlerMethodResolver resolver = this.methodResolverCache.get(handlerClass);
+ if (resolver == null) {
+ synchronized (this.methodResolverCache) {
+ 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.
+ *
+ * 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}
+ * 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.
+ *
+ * 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 HttpOutputMessage instance.
+ *
+ * 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 {@code HandlerMethodResolver}.
+ */
+ @SuppressWarnings("deprecation")
+ private class ServletHandlerMethodResolver extends HandlerMethodResolver {
+
+ private final Map mappings = new HashMap<>();
+
+ 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 pathComparator = pathMatcher.getPatternComparator(lookupPath);
+ Map targetHandlerMethods = new LinkedHashMap<>();
+ Set allowedMethods = new LinkedHashSet<>(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 if (useTypeLevelMapping(request)) {
+ String[] typeLevelPatterns = getTypeLevelMapping().value();
+ for (String typeLevelPattern : typeLevelPatterns) {
+ if (!typeLevelPattern.startsWith("/")) {
+ typeLevelPattern = "/" + typeLevelPattern;
+ }
+ boolean useSuffixPattern = useSuffixPattern(request);
+ if (getMatchingPattern(typeLevelPattern, lookupPath, useSuffixPattern) != null) {
+ if (mappingInfo.matches(request)) {
+ match = true;
+ mappingInfo.addMatchedPattern(typeLevelPattern);
+ } 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 matches = new ArrayList<>(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());
+ }
+ }
+
+ private boolean useTypeLevelMapping(HttpServletRequest request) {
+ if (!hasTypeLevelMapping() || ObjectUtils.isEmpty(getTypeLevelMapping().value())) {
+ return false;
+ }
+ Object value = request.getAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING);
+ return (value != null) ? (Boolean) value : Boolean.TRUE;
+ }
+
+ private boolean useSuffixPattern(HttpServletRequest request) {
+ Object value = request.getAttribute(DefaultAnnotationHandlerMapping.USE_DEFAULT_SUFFIX_PATTERN);
+ return (value != null) ? (Boolean) value : Boolean.TRUE;
+ }
+
+ /**
+ * Determines the combined pattern for the given methodLevelPattern and path.
+ *
+ * Uses the following algorithm:
+ *
+ *
If there is a type-level mapping with path information, it is {@linkplain
+ * PathMatcher#combine(String, String) combined} with the method-level pattern.
+ *
If there is a {@linkplain HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE best matching pattern}
+ * in the request, it is combined with the method-level pattern.
RHIs with {@linkplain AnnotationMethodHandlerAdapter.RequestSpecificMappingInfo#matchedPatterns better
+ * matched paths}
+ * take precedence 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.
+ *
RHIs with one single {@linkplain RequestMappingInfo#methods request method} will be
+ * ordered before those without a method, or with more than one method.
+ *
RHIs with more {@linkplain RequestMappingInfo#params request parameters} will be ordered
+ * before those with less parameters
+ *
+ */
+ static class RequestSpecificMappingInfoComparator implements Comparator {
+
+ private final Comparator pathComparator;
+
+ private final ServerHttpRequest request;
+
+ RequestSpecificMappingInfoComparator(Comparator pathComparator, HttpServletRequest request) {
+ this.pathComparator = pathComparator;
+ this.request = new ServletServerHttpRequest(request);
+ }
+
+ @Override
+ 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 requestAccepts = request.getHeaders().getAccept();
+ MediaType.sortByQualityValue(requestAccepts);
+
+ List info1Accepts = getAcceptHeaderValue(info1);
+ List 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 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 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();
+ }
+ }
+
+}
Index: lams_common/src/java/org/lamsfoundation/lams/context/BeanFactoryLocator.java
===================================================================
diff -u
--- lams_common/src/java/org/lamsfoundation/lams/context/BeanFactoryLocator.java (revision 0)
+++ lams_common/src/java/org/lamsfoundation/lams/context/BeanFactoryLocator.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243)
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2002-2012 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 org.lamsfoundation.lams.context;
+
+import org.springframework.beans.BeansException;
+
+/**
+ * Defines a contract for the lookup, use, and release of a
+ * {@link org.springframework.beans.factory.BeanFactory},
+ * or a {@code BeanFactory} subclass such as an
+ * {@link org.springframework.context.ApplicationContext}.
+ *
+ *
+ * Where this interface is implemented as a singleton class such as
+ * {@link SingletonBeanFactoryLocator}, the Spring team strongly
+ * suggests that it be used sparingly and with caution. By far the vast majority
+ * of the code inside an application is best written in a Dependency Injection
+ * style, where that code is served out of a
+ * {@code BeanFactory}/{@code ApplicationContext} container, and has
+ * its own dependencies supplied by the container when it is created. However,
+ * even such a singleton implementation sometimes has its use in the small glue
+ * layers of code that is sometimes needed to tie other code together. For
+ * example, third party code may try to construct new objects directly, without
+ * the ability to force it to get these objects out of a {@code BeanFactory}.
+ * If the object constructed by the third party code is just a small stub or
+ * proxy, which then uses an implementation of this class to get a
+ * {@code BeanFactory} from which it gets the real object, to which it
+ * delegates, then proper Dependency Injection has been achieved.
+ *
+ *
+ * As another example, in a complex J2EE app with multiple layers, with each
+ * layer having its own {@code ApplicationContext} definition (in a
+ * hierarchy), a class like {@code SingletonBeanFactoryLocator} may be used
+ * to demand load these contexts.
+ *
+ * @author Colin Sampaleanu
+ * @see org.springframework.beans.factory.BeanFactory
+ * @see org.springframework.context.access.DefaultLocatorFactory
+ * @see org.springframework.context.ApplicationContext
+ */
+public interface BeanFactoryLocator {
+
+ /**
+ * Use the {@link org.springframework.beans.factory.BeanFactory} (or derived
+ * interface such as {@link org.springframework.context.ApplicationContext})
+ * specified by the {@code factoryKey} parameter.
+ *
+ * The definition is possibly loaded/created as needed.
+ *
+ * @param factoryKey
+ * a resource name specifying which {@code BeanFactory} the
+ * {@code BeanFactoryLocator} must return for usage. The actual meaning of the
+ * resource name is specific to the implementation of {@code BeanFactoryLocator}.
+ * @return the {@code BeanFactory} instance, wrapped as a {@link BeanFactoryReference} object
+ * @throws BeansException
+ * if there is an error loading or accessing the {@code BeanFactory}
+ */
+ BeanFactoryReference useBeanFactory(String factoryKey) throws BeansException;
+
+}
Index: lams_common/src/java/org/lamsfoundation/lams/context/BeanFactoryReference.java
===================================================================
diff -u
--- lams_common/src/java/org/lamsfoundation/lams/context/BeanFactoryReference.java (revision 0)
+++ lams_common/src/java/org/lamsfoundation/lams/context/BeanFactoryReference.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243)
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2002-2013 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 org.lamsfoundation.lams.context;
+
+import org.springframework.beans.factory.BeanFactory;
+
+/**
+ * Used to track a reference to a {@link BeanFactory} obtained through
+ * a {@link BeanFactoryLocator}.
+ *
+ *
It is safe to call {@link #release()} multiple times, but
+ * {@link #getFactory()} must not be called after calling release.
+ *
+ * @author Colin Sampaleanu
+ * @see BeanFactoryLocator
+ * @see org.springframework.context.access.ContextBeanFactoryReference
+ */
+public interface BeanFactoryReference {
+
+ /**
+ * Return the {@link BeanFactory} instance held by this reference.
+ * @throws IllegalStateException if invoked after {@code release()} has been called
+ */
+ BeanFactory getFactory();
+
+ /**
+ * Indicate that the {@link BeanFactory} instance referred to by this object is not
+ * needed any longer by the client code which obtained the {@link BeanFactoryReference}.
+ *
Depending on the actual implementation of {@link BeanFactoryLocator}, and
+ * the actual type of {@code BeanFactory}, this may possibly not actually
+ * do anything; alternately in the case of a 'closeable' {@code BeanFactory}
+ * or derived class (such as {@link org.springframework.context.ApplicationContext})
+ * may 'close' it, or may 'close' it once no more references remain.
+ *
In an EJB usage scenario this would normally be called from
+ * {@code ejbRemove()} and {@code ejbPassivate()}.
+ *
This is safe to call multiple times.
+ * @see BeanFactoryLocator
+ * @see org.springframework.context.access.ContextBeanFactoryReference
+ * @see org.springframework.context.ConfigurableApplicationContext#close()
+ */
+ void release();
+
+}
Index: lams_common/src/java/org/lamsfoundation/lams/context/BootstrapException.java
===================================================================
diff -u
--- lams_common/src/java/org/lamsfoundation/lams/context/BootstrapException.java (revision 0)
+++ lams_common/src/java/org/lamsfoundation/lams/context/BootstrapException.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243)
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2002-2012 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 org.lamsfoundation.lams.context;
+
+import org.springframework.beans.FatalBeanException;
+
+/**
+ * Exception thrown if a bean factory could not be loaded by a bootstrap class.
+ *
+ * @author Rod Johnson
+ * @since 02.12.2002
+ */
+@SuppressWarnings("serial")
+public class BootstrapException extends FatalBeanException {
+
+ /**
+ * Create a new BootstrapException with the specified message.
+ *
+ * @param msg
+ * the detail message
+ */
+ public BootstrapException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Create a new BootstrapException with the specified message
+ * and root cause.
+ *
+ * @param msg
+ * the detail message
+ * @param cause
+ * the root cause
+ */
+ public BootstrapException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+}
Index: lams_common/src/java/org/lamsfoundation/lams/context/ContextSingletonBeanFactoryLocator.java
===================================================================
diff -u
--- lams_common/src/java/org/lamsfoundation/lams/context/ContextSingletonBeanFactoryLocator.java (revision 0)
+++ lams_common/src/java/org/lamsfoundation/lams/context/ContextSingletonBeanFactoryLocator.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243)
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2002-2012 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 org.lamsfoundation.lams.context;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternUtils;
+
+/**
+ *
+ * Variant of {@link org.springframework.beans.factory.access.SingletonBeanFactoryLocator}
+ * which creates its internal bean factory reference as an
+ * {@link org.springframework.context.ApplicationContext} instead of
+ * SingletonBeanFactoryLocator's simple BeanFactory. For almost all usage scenarios,
+ * this will not make a difference, since within that ApplicationContext or BeanFactory
+ * you are still free to define either BeanFactory or ApplicationContext instances.
+ * The main reason one would need to use this class is if bean post-processing
+ * (or other ApplicationContext specific features are needed in the bean reference
+ * definition itself).
+ *
+ *
+ * Note: This class uses classpath*:beanRefContext.xml
+ * as the default resource location for the bean factory reference definition files.
+ * It is not possible nor legal to share definitions with SingletonBeanFactoryLocator
+ * at the same time.
+ *
+ * @author Colin Sampaleanu
+ * @author Juergen Hoeller
+ * @see org.springframework.beans.factory.access.SingletonBeanFactoryLocator
+ * @see org.springframework.context.access.DefaultLocatorFactory
+ */
+public class ContextSingletonBeanFactoryLocator extends SingletonBeanFactoryLocator {
+
+ private static final String DEFAULT_RESOURCE_LOCATION = "classpath*:beanRefContext.xml";
+
+ /** The keyed singleton instances */
+ private static final Map instances = new HashMap<>();
+
+ /**
+ * Returns an instance which uses the default "classpath*:beanRefContext.xml", as
+ * the name of the definition file(s). All resources returned by the current
+ * thread's context class loader's {@code getResources} method with this
+ * name will be combined to create a definition, which is just a BeanFactory.
+ *
+ * @return the corresponding BeanFactoryLocator instance
+ * @throws BeansException
+ * in case of factory loading failure
+ */
+ public static BeanFactoryLocator getInstance() throws BeansException {
+ return ContextSingletonBeanFactoryLocator.getInstance(null);
+ }
+
+ /**
+ * Returns an instance which uses the specified selector, as the name of the
+ * definition file(s). In the case of a name with a Spring "classpath*:" prefix,
+ * or with no prefix, which is treated the same, the current thread's context class
+ * loader's {@code getResources} method will be called with this value to get
+ * all resources having that name. These resources will then be combined to form a
+ * definition. In the case where the name uses a Spring "classpath:" prefix, or
+ * a standard URL prefix, then only one resource file will be loaded as the
+ * definition.
+ *
+ * @param selector
+ * the location of the resource(s) which will be read and
+ * combined to form the definition for the BeanFactoryLocator instance.
+ * Any such files must form a valid ApplicationContext definition.
+ * @return the corresponding BeanFactoryLocator instance
+ * @throws BeansException
+ * in case of factory loading failure
+ */
+ public static BeanFactoryLocator getInstance(String selector) throws BeansException {
+ String resourceLocation = selector;
+ if (resourceLocation == null) {
+ resourceLocation = DEFAULT_RESOURCE_LOCATION;
+ }
+
+ // For backwards compatibility, we prepend "classpath*:" to the selector name if there
+ // is no other prefix (i.e. "classpath*:", "classpath:", or some URL prefix).
+ if (!ResourcePatternUtils.isUrl(resourceLocation)) {
+ resourceLocation = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resourceLocation;
+ }
+
+ synchronized (instances) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("ContextSingletonBeanFactoryLocator.getInstance(): instances.hashCode="
+ + instances.hashCode() + ", instances=" + instances);
+ }
+ BeanFactoryLocator bfl = instances.get(resourceLocation);
+ if (bfl == null) {
+ bfl = new ContextSingletonBeanFactoryLocator(resourceLocation);
+ instances.put(resourceLocation, bfl);
+ }
+ return bfl;
+ }
+ }
+
+ /**
+ * Constructor which uses the specified name as the resource name
+ * of the definition file(s).
+ *
+ * @param resourceLocation
+ * the Spring resource location to use
+ * (either a URL or a "classpath:" / "classpath*:" pseudo URL)
+ */
+ protected ContextSingletonBeanFactoryLocator(String resourceLocation) {
+ super(resourceLocation);
+ }
+
+ /**
+ * Overrides the default method to create definition object as an ApplicationContext
+ * instead of the default BeanFactory. This does not affect what can actually
+ * be loaded by that definition.
+ *
+ * The default implementation simply builds a
+ * {@link org.springframework.context.support.ClassPathXmlApplicationContext}.
+ */
+ @Override
+ protected BeanFactory createDefinition(String resourceLocation, String factoryKey) {
+ return new ClassPathXmlApplicationContext(new String[] { resourceLocation }, false);
+ }
+
+ /**
+ * Overrides the default method to refresh the ApplicationContext, invoking
+ * {@link ConfigurableApplicationContext#refresh ConfigurableApplicationContext.refresh()}.
+ */
+ @Override
+ protected void initializeDefinition(BeanFactory groupDef) {
+ if (groupDef instanceof ConfigurableApplicationContext) {
+ ((ConfigurableApplicationContext) groupDef).refresh();
+ }
+ }
+
+ /**
+ * Overrides the default method to operate on an ApplicationContext, invoking
+ * {@link ConfigurableApplicationContext#refresh ConfigurableApplicationContext.close()}.
+ */
+ @Override
+ protected void destroyDefinition(BeanFactory groupDef, String selector) {
+ if (groupDef instanceof ConfigurableApplicationContext) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("Context group with selector '" + selector
+ + "' being released, as there are no more references to it");
+ }
+ ((ConfigurableApplicationContext) groupDef).close();
+ }
+ }
+
+}
Index: lams_common/src/java/org/lamsfoundation/lams/context/DefaultAnnotationHandlerMapping.java
===================================================================
diff -u
--- lams_common/src/java/org/lamsfoundation/lams/context/DefaultAnnotationHandlerMapping.java (revision 0)
+++ lams_common/src/java/org/lamsfoundation/lams/context/DefaultAnnotationHandlerMapping.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243)
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2002-2015 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 org.lamsfoundation.lams.context;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.stereotype.Controller;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.ServletRequestBindingException;
+import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping;
+
+/**
+ * Implementation of the {@link org.springframework.web.servlet.HandlerMapping}
+ * interface that maps handlers based on HTTP paths expressed through the
+ * {@link RequestMapping} annotation at the type or method level.
+ *
+ *
+ * Registered by default in {@link org.springframework.web.servlet.DispatcherServlet}
+ * on Java 5+. NOTE: If you define custom HandlerMapping beans in your
+ * DispatcherServlet context, you need to add a DefaultAnnotationHandlerMapping bean
+ * explicitly, since custom HandlerMapping beans replace the default mapping strategies.
+ * Defining a DefaultAnnotationHandlerMapping also allows for registering custom
+ * interceptors:
+ *
+ *
+ *
+ * Annotated controllers are usually marked with the {@link Controller} stereotype
+ * at the type level. This is not strictly necessary when {@link RequestMapping} is
+ * applied at the type level (since such a handler usually implements the
+ * {@link org.springframework.web.servlet.mvc.Controller} interface). However,
+ * {@link Controller} is required for detecting {@link RequestMapping} annotations
+ * at the method level if {@link RequestMapping} is not present at the type level.
+ *
+ *
+ * NOTE: Method-level mappings are only allowed to narrow the mapping
+ * expressed at the class level (if any). HTTP paths need to uniquely map onto
+ * specific handler beans, with any given HTTP path only allowed to be mapped
+ * onto one specific handler bean (not spread across multiple handler beans).
+ * It is strongly recommended to co-locate related handler methods into the same bean.
+ *
+ *
+ * The {@link AnnotationMethodHandlerAdapter} is responsible for processing
+ * annotated handler methods, as mapped by this HandlerMapping. For
+ * {@link RequestMapping} at the type level, specific HandlerAdapters such as
+ * {@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter} apply.
+ *
+ * @author Juergen Hoeller
+ * @author Arjen Poutsma
+ * @since 2.5
+ * @see RequestMapping
+ * @see AnnotationMethodHandlerAdapter
+ * @deprecated as of Spring 3.2, in favor of
+ * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
+ * RequestMappingHandlerMapping}
+ */
+@Deprecated
+public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandlerMapping {
+
+ static final String USE_DEFAULT_SUFFIX_PATTERN = DefaultAnnotationHandlerMapping.class.getName()
+ + ".useDefaultSuffixPattern";
+
+ private boolean useDefaultSuffixPattern = true;
+
+ private final Map, RequestMapping> cachedMappings = new HashMap<>();
+
+ /**
+ * Set whether to register paths using the default suffix pattern as well:
+ * i.e. whether "/users" should be registered as "/users.*" and "/users/" too.
+ *
+ * Default is "true". Turn this convention off if you intend to interpret
+ * your {@code @RequestMapping} paths strictly.
+ *
+ * Note that paths which include a ".xxx" suffix or end with "/" already will not be
+ * transformed using the default suffix pattern in any case.
+ */
+ public void setUseDefaultSuffixPattern(boolean useDefaultSuffixPattern) {
+ this.useDefaultSuffixPattern = useDefaultSuffixPattern;
+ }
+
+ /**
+ * Checks for presence of the {@link org.springframework.web.bind.annotation.RequestMapping}
+ * annotation on the handler class and on any of its methods.
+ */
+ @Override
+ protected String[] determineUrlsForHandler(String beanName) {
+ ApplicationContext context = getApplicationContext();
+ Class> handlerType = context.getType(beanName);
+ RequestMapping mapping = context.findAnnotationOnBean(beanName, RequestMapping.class);
+ if (mapping != null) {
+ // @RequestMapping found at type level
+ this.cachedMappings.put(handlerType, mapping);
+ Set urls = new LinkedHashSet<>();
+ String[] typeLevelPatterns = mapping.value();
+ if (typeLevelPatterns.length > 0) {
+ // @RequestMapping specifies paths at type level
+ String[] methodLevelPatterns = determineUrlsForHandlerMethods(handlerType, true);
+ for (String typeLevelPattern : typeLevelPatterns) {
+ if (!typeLevelPattern.startsWith("/")) {
+ typeLevelPattern = "/" + typeLevelPattern;
+ }
+ boolean hasEmptyMethodLevelMappings = false;
+ for (String methodLevelPattern : methodLevelPatterns) {
+ if (methodLevelPattern == null) {
+ hasEmptyMethodLevelMappings = true;
+ } else {
+ String combinedPattern = getPathMatcher().combine(typeLevelPattern, methodLevelPattern);
+ addUrlsForPath(urls, combinedPattern);
+ }
+ }
+ if (hasEmptyMethodLevelMappings
+ || org.springframework.web.servlet.mvc.Controller.class.isAssignableFrom(handlerType)) {
+ addUrlsForPath(urls, typeLevelPattern);
+ }
+ }
+ return StringUtils.toStringArray(urls);
+ } else {
+ // actual paths specified by @RequestMapping at method level
+ return determineUrlsForHandlerMethods(handlerType, false);
+ }
+ } else if (AnnotationUtils.findAnnotation(handlerType, Controller.class) != null) {
+ // @RequestMapping to be introspected at method level
+ return determineUrlsForHandlerMethods(handlerType, false);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Derive URL mappings from the handler's method-level mappings.
+ *
+ * @param handlerType
+ * the handler type to introspect
+ * @param hasTypeLevelMapping
+ * whether the method-level mappings are nested
+ * within a type-level mapping
+ * @return the array of mapped URLs
+ */
+ protected String[] determineUrlsForHandlerMethods(Class> handlerType, final boolean hasTypeLevelMapping) {
+ String[] subclassResult = determineUrlsForHandlerMethods(handlerType);
+ if (subclassResult != null) {
+ return subclassResult;
+ }
+
+ final Set urls = new LinkedHashSet<>();
+ Set> handlerTypes = new LinkedHashSet<>();
+ handlerTypes.add(handlerType);
+ handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces()));
+ for (Class> currentHandlerType : handlerTypes) {
+ ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
+ @Override
+ public void doWith(Method method) {
+ RequestMapping mapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
+ if (mapping != null) {
+ String[] mappedPatterns = mapping.value();
+ if (mappedPatterns.length > 0) {
+ for (String mappedPattern : mappedPatterns) {
+ if (!hasTypeLevelMapping && !mappedPattern.startsWith("/")) {
+ mappedPattern = "/" + mappedPattern;
+ }
+ addUrlsForPath(urls, mappedPattern);
+ }
+ } else if (hasTypeLevelMapping) {
+ // empty method-level RequestMapping
+ urls.add(null);
+ }
+ }
+ }
+ }, ReflectionUtils.USER_DECLARED_METHODS);
+ }
+ return StringUtils.toStringArray(urls);
+ }
+
+ /**
+ * Derive URL mappings from the handler's method-level mappings.
+ *
+ * @param handlerType
+ * the handler type to introspect
+ * @return the array of mapped URLs
+ */
+ protected String[] determineUrlsForHandlerMethods(Class> handlerType) {
+ return null;
+ }
+
+ /**
+ * Add URLs and/or URL patterns for the given path.
+ *
+ * @param urls
+ * the Set of URLs for the current bean
+ * @param path
+ * the currently introspected path
+ */
+ protected void addUrlsForPath(Set urls, String path) {
+ urls.add(path);
+ if (this.useDefaultSuffixPattern && path.indexOf('.') == -1 && !path.endsWith("/")) {
+ urls.add(path + ".*");
+ urls.add(path + "/");
+ }
+ }
+
+ /**
+ * Validate the given annotated handler against the current request.
+ *
+ * @see #validateMapping
+ */
+ @Override
+ protected void validateHandler(Object handler, HttpServletRequest request) throws Exception {
+ RequestMapping mapping = this.cachedMappings.get(handler.getClass());
+ if (mapping == null) {
+ mapping = AnnotationUtils.findAnnotation(handler.getClass(), RequestMapping.class);
+ }
+ if (mapping != null) {
+ validateMapping(mapping, request);
+ }
+ request.setAttribute(USE_DEFAULT_SUFFIX_PATTERN, this.useDefaultSuffixPattern);
+ }
+
+ /**
+ * Validate the given type-level mapping metadata against the current request,
+ * checking HTTP request method and parameter conditions.
+ *
+ * @param mapping
+ * the mapping metadata to validate
+ * @param request
+ * current HTTP request
+ * @throws Exception
+ * if validation failed
+ */
+ protected void validateMapping(RequestMapping mapping, HttpServletRequest request) throws Exception {
+ RequestMethod[] mappedMethods = mapping.method();
+ if (!ServletAnnotationMappingUtils.checkRequestMethod(mappedMethods, request)) {
+ String[] supportedMethods = new String[mappedMethods.length];
+ for (int i = 0; i < mappedMethods.length; i++) {
+ supportedMethods[i] = mappedMethods[i].name();
+ }
+ throw new HttpRequestMethodNotSupportedException(request.getMethod(), supportedMethods);
+ }
+
+ String[] mappedParams = mapping.params();
+ if (!ServletAnnotationMappingUtils.checkParameters(mappedParams, request)) {
+ throw new UnsatisfiedServletRequestParameterException(mappedParams, request.getParameterMap());
+ }
+
+ String[] mappedHeaders = mapping.headers();
+ if (!ServletAnnotationMappingUtils.checkHeaders(mappedHeaders, request)) {
+ throw new ServletRequestBindingException("Header conditions \""
+ + StringUtils.arrayToDelimitedString(mappedHeaders, ", ") + "\" not met for actual request");
+ }
+ }
+
+ @Override
+ protected boolean supportsTypeLevelMappings() {
+ return true;
+ }
+}
Index: lams_common/src/java/org/lamsfoundation/lams/context/HandlerMethodInvocationException.java
===================================================================
diff -u
--- lams_common/src/java/org/lamsfoundation/lams/context/HandlerMethodInvocationException.java (revision 0)
+++ lams_common/src/java/org/lamsfoundation/lams/context/HandlerMethodInvocationException.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243)
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2002-2012 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 org.lamsfoundation.lams.context;
+
+import java.lang.reflect.Method;
+
+import org.springframework.core.NestedRuntimeException;
+
+/**
+ * Exception indicating that the execution of an annotated MVC handler method failed.
+ *
+ * @author Juergen Hoeller
+ * @since 2.5.6
+ * @see HandlerMethodInvoker#invokeHandlerMethod
+ * @deprecated as of 4.3, in favor of the {@code HandlerMethod}-based MVC infrastructure
+ */
+@Deprecated
+@SuppressWarnings("serial")
+public class HandlerMethodInvocationException extends NestedRuntimeException {
+
+ /**
+ * Create a new HandlerMethodInvocationException for the given Method handle and cause.
+ * @param handlerMethod the handler method handle
+ * @param cause the cause of the invocation failure
+ */
+ public HandlerMethodInvocationException(Method handlerMethod, Throwable cause) {
+ super("Failed to invoke handler method [" + handlerMethod + "]", cause);
+ }
+
+}
Index: lams_common/src/java/org/lamsfoundation/lams/context/HandlerMethodInvoker.java
===================================================================
diff -u
--- lams_common/src/java/org/lamsfoundation/lams/context/HandlerMethodInvoker.java (revision 0)
+++ lams_common/src/java/org/lamsfoundation/lams/context/HandlerMethodInvoker.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243)
@@ -0,0 +1,901 @@
+/*
+ * Copyright 2002-2015 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 org.lamsfoundation.lams.context;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.BridgeMethodResolver;
+import org.springframework.core.Conventions;
+import org.springframework.core.GenericTypeResolver;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.SynthesizingMethodParameter;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpInputMessage;
+import org.springframework.http.HttpOutputMessage;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.ui.ExtendedModelMap;
+import org.springframework.ui.Model;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.validation.BindException;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.Errors;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.HttpMediaTypeNotSupportedException;
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.CookieValue;
+import org.springframework.web.bind.annotation.InitBinder;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ValueConstants;
+import org.springframework.web.bind.support.DefaultSessionAttributeStore;
+import org.springframework.web.bind.support.SessionAttributeStore;
+import org.springframework.web.bind.support.SessionStatus;
+import org.springframework.web.bind.support.SimpleSessionStatus;
+import org.springframework.web.bind.support.WebArgumentResolver;
+import org.springframework.web.bind.support.WebBindingInitializer;
+import org.springframework.web.bind.support.WebRequestDataBinder;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartRequest;
+
+/**
+ * Support class for invoking an annotated handler method. Operates on the introspection
+ * results of a {@link HandlerMethodResolver} for a specific handler type.
+ *
+ *