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: + *

    + *
  1. If there is a type-level mapping with path information, it is {@linkplain + * PathMatcher#combine(String, String) combined} with the method-level pattern.
  2. + *
  3. If there is a {@linkplain HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE best matching pattern} + * in the request, it is combined with the method-level pattern.
  4. + *
  5. Otherwise, the method-level pattern is returned.
  6. + *
+ */ + private String getCombinedPattern(String methodLevelPattern, String lookupPath, HttpServletRequest request) { + boolean useSuffixPattern = useSuffixPattern(request); + if (useTypeLevelMapping(request)) { + String[] typeLevelPatterns = getTypeLevelMapping().value(); + for (String typeLevelPattern : typeLevelPatterns) { + if (!typeLevelPattern.startsWith("/")) { + typeLevelPattern = "/" + typeLevelPattern; + } + String combinedPattern = pathMatcher.combine(typeLevelPattern, methodLevelPattern); + String matchingPattern = getMatchingPattern(combinedPattern, lookupPath, useSuffixPattern); + if (matchingPattern != null) { + return matchingPattern; + } + } + return null; + } + String bestMatchingPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); + if (StringUtils.hasText(bestMatchingPattern) && bestMatchingPattern.endsWith("*")) { + String combinedPattern = pathMatcher.combine(bestMatchingPattern, methodLevelPattern); + String matchingPattern = getMatchingPattern(combinedPattern, lookupPath, useSuffixPattern); + if (matchingPattern != null && !matchingPattern.equals(bestMatchingPattern)) { + return matchingPattern; + } + } + return getMatchingPattern(methodLevelPattern, lookupPath, useSuffixPattern); + } + + private String getMatchingPattern(String pattern, String lookupPath, boolean useSuffixPattern) { + if (pattern.equals(lookupPath)) { + return pattern; + } + boolean hasSuffix = pattern.indexOf('.') != -1; + if (useSuffixPattern && !hasSuffix) { + String patternWithSuffix = pattern + ".*"; + if (pathMatcher.match(patternWithSuffix, lookupPath)) { + return patternWithSuffix; + } + } + if (pathMatcher.match(pattern, lookupPath)) { + return pattern; + } + boolean endsWithSlash = pattern.endsWith("/"); + if (useSuffixPattern && !endsWithSlash) { + String patternWithSlash = pattern + "/"; + if (pathMatcher.match(patternWithSlash, lookupPath)) { + return patternWithSlash; + } + } + return null; + } + + @SuppressWarnings("unchecked") + private void extractHandlerMethodUriTemplates(String mappedPattern, String lookupPath, + HttpServletRequest request) { + Map variables = (Map) 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); + Map decodedVariables = urlPathHelper.decodePathVariables(request, variables); + request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedVariables); + } + } + } + + /** + * Servlet-specific subclass of {@code HandlerMethodInvoker}. + */ + @SuppressWarnings("deprecation") + 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, 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 AnnotationMethodHandlerAdapter.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 AnnotationMethodHandlerAdapter.this.createHttpInputMessage(servletRequest); + } + + @Override + protected HttpOutputMessage createHttpOutputMessage(NativeWebRequest webRequest) throws Exception { + HttpServletResponse servletResponse = (HttpServletResponse) webRequest.getNativeResponse(); + return AnnotationMethodHandlerAdapter.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, 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, Class paramType, NativeWebRequest webRequest) + throws Exception { + + HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); + Map uriTemplateVariables = (Map) 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 == 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 responseStatus = AnnotatedElementUtils.findMergedAnnotation(handlerMethod, + ResponseStatus.class); + if (responseStatus != null) { + HttpStatus statusCode = responseStatus.code(); + String reason = responseStatus.reason(); + if (!StringUtils.hasText(reason)) { + webRequest.getResponse().setStatus(statusCode.value()); + } else { + webRequest.getResponse().sendError(statusCode.value(), reason); + } + + // to be picked up by the RedirectView + webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, statusCode); + + this.responseArgumentUsed = true; + } + + // 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) 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); + } + + 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); + } else { + // flush headers + outputMessage.getBody(); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private void writeWithMessageConverters(Object returnValue, HttpInputMessage inputMessage, + HttpOutputMessage outputMessage) throws IOException, HttpMediaTypeNotAcceptableException { + + List acceptedMediaTypes = inputMessage.getHeaders().getAccept(); + if (acceptedMediaTypes.isEmpty()) { + acceptedMediaTypes = Collections.singletonList(MediaType.ALL); + } + MediaType.sortByQualityValue(acceptedMediaTypes); + Class returnValueType = returnValue.getClass(); + List allSupportedMediaTypes = new ArrayList<>(); + if (getMessageConverters() != null) { + for (MediaType acceptedMediaType : acceptedMediaTypes) { + for (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 (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 (this.patterns.length > 0); + } + + public String[] getPatterns() { + return this.patterns; + } + + public int getMethodCount() { + return this.methods.length; + } + + public int getParamCount() { + return this.params.length; + } + + public int getHeaderCount() { + return this.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 methodNames() { + Set methodNames = new LinkedHashSet<>(this.methods.length); + for (RequestMethod method : this.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(this.patterns)); + if (this.methods.length > 0) { + builder.append(','); + builder.append(Arrays.asList(this.methods)); + } + if (this.headers.length > 0) { + builder.append(','); + builder.append(Arrays.asList(this.headers)); + } + if (this.params.length > 0) { + builder.append(','); + builder.append(Arrays.asList(this.params)); + } + return builder.toString(); + } + } + + /** + * Subclass of {@link RequestMappingInfo} that holds request-specific data. + */ + static class RequestSpecificMappingInfo extends RequestMappingInfo { + + private final List matchedPatterns = new ArrayList<>(); + + 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 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: + *
    + *
  • 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: + * + *

    + * <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
    + *   <property name="interceptors">
    + *     ...
    + *   </property>
    + * </bean>
    + * 
    + * + * 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. + * + *

    Used by {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter} + * and {@link org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter}. + * + * @author Juergen Hoeller + * @author Arjen Poutsma + * @since 2.5.2 + * @see #invokeHandlerMethod + * @deprecated as of 4.3, in favor of the {@code HandlerMethod}-based MVC infrastructure + */ +@Deprecated +public class HandlerMethodInvoker { + + private static final String MODEL_KEY_PREFIX_STALE = SessionAttributeStore.class.getName() + ".STALE."; + + /** We'll create a lot of these objects, so we don't want a new logger every time. */ + private static final Log logger = LogFactory.getLog(HandlerMethodInvoker.class); + + private final HandlerMethodResolver methodResolver; + + private final WebBindingInitializer bindingInitializer; + + private final SessionAttributeStore sessionAttributeStore; + + private final ParameterNameDiscoverer parameterNameDiscoverer; + + private final WebArgumentResolver[] customArgumentResolvers; + + private final HttpMessageConverter[] messageConverters; + + private final SimpleSessionStatus sessionStatus = new SimpleSessionStatus(); + + + public HandlerMethodInvoker(HandlerMethodResolver methodResolver) { + this(methodResolver, null); + } + + public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer) { + this(methodResolver, bindingInitializer, new DefaultSessionAttributeStore(), null, null, null); + } + + public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer, + SessionAttributeStore sessionAttributeStore, ParameterNameDiscoverer parameterNameDiscoverer, + WebArgumentResolver[] customArgumentResolvers, HttpMessageConverter[] messageConverters) { + + this.methodResolver = methodResolver; + this.bindingInitializer = bindingInitializer; + this.sessionAttributeStore = sessionAttributeStore; + this.parameterNameDiscoverer = parameterNameDiscoverer; + this.customArgumentResolvers = customArgumentResolvers; + this.messageConverters = messageConverters; + } + + + public final Object invokeHandlerMethod(Method handlerMethod, Object handler, + NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception { + + Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod); + try { + boolean debug = logger.isDebugEnabled(); + for (String attrName : this.methodResolver.getActualSessionAttributeNames()) { + Object attrValue = this.sessionAttributeStore.retrieveAttribute(webRequest, attrName); + if (attrValue != null) { + implicitModel.addAttribute(attrName, attrValue); + } + } + for (Method attributeMethod : this.methodResolver.getModelAttributeMethods()) { + Method attributeMethodToInvoke = BridgeMethodResolver.findBridgedMethod(attributeMethod); + Object[] args = resolveHandlerArguments(attributeMethodToInvoke, handler, webRequest, implicitModel); + if (debug) { + logger.debug("Invoking model attribute method: " + attributeMethodToInvoke); + } + String attrName = AnnotationUtils.findAnnotation(attributeMethod, ModelAttribute.class).value(); + if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) { + continue; + } + ReflectionUtils.makeAccessible(attributeMethodToInvoke); + Object attrValue = attributeMethodToInvoke.invoke(handler, args); + if ("".equals(attrName)) { + Class resolvedType = GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass()); + attrName = Conventions.getVariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue); + } + if (!implicitModel.containsAttribute(attrName)) { + implicitModel.addAttribute(attrName, attrValue); + } + } + Object[] args = resolveHandlerArguments(handlerMethodToInvoke, handler, webRequest, implicitModel); + if (debug) { + logger.debug("Invoking request handler method: " + handlerMethodToInvoke); + } + ReflectionUtils.makeAccessible(handlerMethodToInvoke); + return handlerMethodToInvoke.invoke(handler, args); + } + catch (IllegalStateException ex) { + // Internal assertion failed (e.g. invalid signature): + // throw exception with full handler method context... + throw new HandlerMethodInvocationException(handlerMethodToInvoke, ex); + } + catch (InvocationTargetException ex) { + // User-defined @ModelAttribute/@InitBinder/@RequestMapping method threw an exception... + ReflectionUtils.rethrowException(ex.getTargetException()); + return null; + } + } + + public final void updateModelAttributes(Object handler, Map mavModel, + ExtendedModelMap implicitModel, NativeWebRequest webRequest) throws Exception { + + if (this.methodResolver.hasSessionAttributes() && this.sessionStatus.isComplete()) { + for (String attrName : this.methodResolver.getActualSessionAttributeNames()) { + this.sessionAttributeStore.cleanupAttribute(webRequest, attrName); + } + } + + // Expose model attributes as session attributes, if required. + // Expose BindingResults for all attributes, making custom editors available. + Map model = (mavModel != null ? mavModel : implicitModel); + if (model != null) { + try { + String[] originalAttrNames = model.keySet().toArray(new String[model.size()]); + for (String attrName : originalAttrNames) { + Object attrValue = model.get(attrName); + boolean isSessionAttr = this.methodResolver.isSessionAttribute( + attrName, (attrValue != null ? attrValue.getClass() : null)); + if (isSessionAttr) { + if (this.sessionStatus.isComplete()) { + implicitModel.put(MODEL_KEY_PREFIX_STALE + attrName, Boolean.TRUE); + } + else if (!implicitModel.containsKey(MODEL_KEY_PREFIX_STALE + attrName)) { + this.sessionAttributeStore.storeAttribute(webRequest, attrName, attrValue); + } + } + if (!attrName.startsWith(BindingResult.MODEL_KEY_PREFIX) && + (isSessionAttr || isBindingCandidate(attrValue))) { + String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + attrName; + if (mavModel != null && !model.containsKey(bindingResultKey)) { + WebDataBinder binder = createBinder(webRequest, attrValue, attrName); + initBinder(handler, attrName, binder, webRequest); + mavModel.put(bindingResultKey, binder.getBindingResult()); + } + } + } + } + catch (InvocationTargetException ex) { + // User-defined @InitBinder method threw an exception... + ReflectionUtils.rethrowException(ex.getTargetException()); + } + } + } + + + private Object[] resolveHandlerArguments(Method handlerMethod, Object handler, + NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception { + + Class[] paramTypes = handlerMethod.getParameterTypes(); + Object[] args = new Object[paramTypes.length]; + + for (int i = 0; i < args.length; i++) { + MethodParameter methodParam = new SynthesizingMethodParameter(handlerMethod, i); + methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer); + GenericTypeResolver.resolveParameterType(methodParam, handler.getClass()); + String paramName = null; + String headerName = null; + boolean requestBodyFound = false; + String cookieName = null; + String pathVarName = null; + String attrName = null; + boolean required = false; + String defaultValue = null; + boolean validate = false; + Object[] validationHints = null; + int annotationsFound = 0; + Annotation[] paramAnns = methodParam.getParameterAnnotations(); + + for (Annotation paramAnn : paramAnns) { + if (RequestParam.class.isInstance(paramAnn)) { + RequestParam requestParam = (RequestParam) paramAnn; + paramName = requestParam.name(); + required = requestParam.required(); + defaultValue = parseDefaultValueAttribute(requestParam.defaultValue()); + annotationsFound++; + } + else if (RequestHeader.class.isInstance(paramAnn)) { + RequestHeader requestHeader = (RequestHeader) paramAnn; + headerName = requestHeader.name(); + required = requestHeader.required(); + defaultValue = parseDefaultValueAttribute(requestHeader.defaultValue()); + annotationsFound++; + } + else if (RequestBody.class.isInstance(paramAnn)) { + requestBodyFound = true; + annotationsFound++; + } + else if (CookieValue.class.isInstance(paramAnn)) { + CookieValue cookieValue = (CookieValue) paramAnn; + cookieName = cookieValue.name(); + required = cookieValue.required(); + defaultValue = parseDefaultValueAttribute(cookieValue.defaultValue()); + annotationsFound++; + } + else if (PathVariable.class.isInstance(paramAnn)) { + PathVariable pathVar = (PathVariable) paramAnn; + pathVarName = pathVar.value(); + annotationsFound++; + } + else if (ModelAttribute.class.isInstance(paramAnn)) { + ModelAttribute attr = (ModelAttribute) paramAnn; + attrName = attr.value(); + annotationsFound++; + } + else if (Value.class.isInstance(paramAnn)) { + defaultValue = ((Value) paramAnn).value(); + } + else { + Validated validatedAnn = AnnotationUtils.getAnnotation(paramAnn, Validated.class); + if (validatedAnn != null || paramAnn.annotationType().getSimpleName().startsWith("Valid")) { + validate = true; + Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(paramAnn)); + validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[]{hints}); + } + } + } + + if (annotationsFound > 1) { + throw new IllegalStateException("Handler parameter annotations are exclusive choices - " + + "do not specify more than one such annotation on the same parameter: " + handlerMethod); + } + + if (annotationsFound == 0) { + Object argValue = resolveCommonArgument(methodParam, webRequest); + if (argValue != WebArgumentResolver.UNRESOLVED) { + args[i] = argValue; + } + else if (defaultValue != null) { + args[i] = resolveDefaultValue(defaultValue); + } + else { + Class paramType = methodParam.getParameterType(); + if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) { + if (!paramType.isAssignableFrom(implicitModel.getClass())) { + throw new IllegalStateException("Argument [" + paramType.getSimpleName() + "] is of type " + + "Model or Map but is not assignable from the actual model. You may need to switch " + + "newer MVC infrastructure classes to use this argument."); + } + args[i] = implicitModel; + } + else if (SessionStatus.class.isAssignableFrom(paramType)) { + args[i] = this.sessionStatus; + } + else if (HttpEntity.class.isAssignableFrom(paramType)) { + args[i] = resolveHttpEntityRequest(methodParam, webRequest); + } + else if (Errors.class.isAssignableFrom(paramType)) { + throw new IllegalStateException("Errors/BindingResult argument declared " + + "without preceding model attribute. Check your handler method signature!"); + } + else if (BeanUtils.isSimpleProperty(paramType)) { + paramName = ""; + } + else { + attrName = ""; + } + } + } + + if (paramName != null) { + args[i] = resolveRequestParam(paramName, required, defaultValue, methodParam, webRequest, handler); + } + else if (headerName != null) { + args[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler); + } + else if (requestBodyFound) { + args[i] = resolveRequestBody(methodParam, webRequest, handler); + } + else if (cookieName != null) { + args[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler); + } + else if (pathVarName != null) { + args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler); + } + else if (attrName != null) { + WebDataBinder binder = + resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler); + boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1])); + if (binder.getTarget() != null) { + doBind(binder, webRequest, validate, validationHints, !assignBindingResult); + } + args[i] = binder.getTarget(); + if (assignBindingResult) { + args[i + 1] = binder.getBindingResult(); + i++; + } + implicitModel.putAll(binder.getBindingResult().getModel()); + } + } + + return args; + } + + protected void initBinder(Object handler, String attrName, WebDataBinder binder, NativeWebRequest webRequest) + throws Exception { + + if (this.bindingInitializer != null) { + this.bindingInitializer.initBinder(binder, webRequest); + } + if (handler != null) { + Set initBinderMethods = this.methodResolver.getInitBinderMethods(); + if (!initBinderMethods.isEmpty()) { + boolean debug = logger.isDebugEnabled(); + for (Method initBinderMethod : initBinderMethods) { + Method methodToInvoke = BridgeMethodResolver.findBridgedMethod(initBinderMethod); + String[] targetNames = AnnotationUtils.findAnnotation(initBinderMethod, InitBinder.class).value(); + if (targetNames.length == 0 || Arrays.asList(targetNames).contains(attrName)) { + Object[] initBinderArgs = + resolveInitBinderArguments(handler, methodToInvoke, binder, webRequest); + if (debug) { + logger.debug("Invoking init-binder method: " + methodToInvoke); + } + ReflectionUtils.makeAccessible(methodToInvoke); + Object returnValue = methodToInvoke.invoke(handler, initBinderArgs); + if (returnValue != null) { + throw new IllegalStateException( + "InitBinder methods must not have a return value: " + methodToInvoke); + } + } + } + } + } + } + + private Object[] resolveInitBinderArguments(Object handler, Method initBinderMethod, + WebDataBinder binder, NativeWebRequest webRequest) throws Exception { + + Class[] initBinderParams = initBinderMethod.getParameterTypes(); + Object[] initBinderArgs = new Object[initBinderParams.length]; + + for (int i = 0; i < initBinderArgs.length; i++) { + MethodParameter methodParam = new SynthesizingMethodParameter(initBinderMethod, i); + methodParam.initParameterNameDiscovery(this.parameterNameDiscoverer); + GenericTypeResolver.resolveParameterType(methodParam, handler.getClass()); + String paramName = null; + boolean paramRequired = false; + String paramDefaultValue = null; + String pathVarName = null; + Annotation[] paramAnns = methodParam.getParameterAnnotations(); + + for (Annotation paramAnn : paramAnns) { + if (RequestParam.class.isInstance(paramAnn)) { + RequestParam requestParam = (RequestParam) paramAnn; + paramName = requestParam.name(); + paramRequired = requestParam.required(); + paramDefaultValue = parseDefaultValueAttribute(requestParam.defaultValue()); + break; + } + else if (ModelAttribute.class.isInstance(paramAnn)) { + throw new IllegalStateException( + "@ModelAttribute is not supported on @InitBinder methods: " + initBinderMethod); + } + else if (PathVariable.class.isInstance(paramAnn)) { + PathVariable pathVar = (PathVariable) paramAnn; + pathVarName = pathVar.value(); + } + } + + if (paramName == null && pathVarName == null) { + Object argValue = resolveCommonArgument(methodParam, webRequest); + if (argValue != WebArgumentResolver.UNRESOLVED) { + initBinderArgs[i] = argValue; + } + else { + Class paramType = initBinderParams[i]; + if (paramType.isInstance(binder)) { + initBinderArgs[i] = binder; + } + else if (BeanUtils.isSimpleProperty(paramType)) { + paramName = ""; + } + else { + throw new IllegalStateException("Unsupported argument [" + paramType.getName() + + "] for @InitBinder method: " + initBinderMethod); + } + } + } + + if (paramName != null) { + initBinderArgs[i] = + resolveRequestParam(paramName, paramRequired, paramDefaultValue, methodParam, webRequest, null); + } + else if (pathVarName != null) { + initBinderArgs[i] = resolvePathVariable(pathVarName, methodParam, webRequest, null); + } + } + + return initBinderArgs; + } + + @SuppressWarnings("unchecked") + private Object resolveRequestParam(String paramName, boolean required, String defaultValue, + MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall) + throws Exception { + + Class paramType = methodParam.getParameterType(); + if (Map.class.isAssignableFrom(paramType) && paramName.length() == 0) { + return resolveRequestParamMap((Class>) paramType, webRequest); + } + if (paramName.length() == 0) { + paramName = getRequiredParameterName(methodParam); + } + Object paramValue = null; + MultipartRequest multipartRequest = webRequest.getNativeRequest(MultipartRequest.class); + if (multipartRequest != null) { + List files = multipartRequest.getFiles(paramName); + if (!files.isEmpty()) { + paramValue = (files.size() == 1 ? files.get(0) : files); + } + } + if (paramValue == null) { + String[] paramValues = webRequest.getParameterValues(paramName); + if (paramValues != null) { + paramValue = (paramValues.length == 1 ? paramValues[0] : paramValues); + } + } + if (paramValue == null) { + if (defaultValue != null) { + paramValue = resolveDefaultValue(defaultValue); + } + else if (required) { + raiseMissingParameterException(paramName, paramType); + } + paramValue = checkValue(paramName, paramValue, paramType); + } + WebDataBinder binder = createBinder(webRequest, null, paramName); + initBinder(handlerForInitBinderCall, paramName, binder, webRequest); + return binder.convertIfNecessary(paramValue, paramType, methodParam); + } + + private Map resolveRequestParamMap(Class> mapType, NativeWebRequest webRequest) { + Map parameterMap = webRequest.getParameterMap(); + if (MultiValueMap.class.isAssignableFrom(mapType)) { + MultiValueMap result = new LinkedMultiValueMap(parameterMap.size()); + for (Map.Entry entry : parameterMap.entrySet()) { + for (String value : entry.getValue()) { + result.add(entry.getKey(), value); + } + } + return result; + } + else { + Map result = new LinkedHashMap(parameterMap.size()); + for (Map.Entry entry : parameterMap.entrySet()) { + if (entry.getValue().length > 0) { + result.put(entry.getKey(), entry.getValue()[0]); + } + } + return result; + } + } + + @SuppressWarnings("unchecked") + private Object resolveRequestHeader(String headerName, boolean required, String defaultValue, + MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall) + throws Exception { + + Class paramType = methodParam.getParameterType(); + if (Map.class.isAssignableFrom(paramType)) { + return resolveRequestHeaderMap((Class>) paramType, webRequest); + } + if (headerName.length() == 0) { + headerName = getRequiredParameterName(methodParam); + } + Object headerValue = null; + String[] headerValues = webRequest.getHeaderValues(headerName); + if (headerValues != null) { + headerValue = (headerValues.length == 1 ? headerValues[0] : headerValues); + } + if (headerValue == null) { + if (defaultValue != null) { + headerValue = resolveDefaultValue(defaultValue); + } + else if (required) { + raiseMissingHeaderException(headerName, paramType); + } + headerValue = checkValue(headerName, headerValue, paramType); + } + WebDataBinder binder = createBinder(webRequest, null, headerName); + initBinder(handlerForInitBinderCall, headerName, binder, webRequest); + return binder.convertIfNecessary(headerValue, paramType, methodParam); + } + + private Map resolveRequestHeaderMap(Class> mapType, NativeWebRequest webRequest) { + if (MultiValueMap.class.isAssignableFrom(mapType)) { + MultiValueMap result; + if (HttpHeaders.class.isAssignableFrom(mapType)) { + result = new HttpHeaders(); + } + else { + result = new LinkedMultiValueMap(); + } + for (Iterator iterator = webRequest.getHeaderNames(); iterator.hasNext();) { + String headerName = iterator.next(); + for (String headerValue : webRequest.getHeaderValues(headerName)) { + result.add(headerName, headerValue); + } + } + return result; + } + else { + Map result = new LinkedHashMap(); + for (Iterator iterator = webRequest.getHeaderNames(); iterator.hasNext();) { + String headerName = iterator.next(); + String headerValue = webRequest.getHeader(headerName); + result.put(headerName, headerValue); + } + return result; + } + } + + /** + * Resolves the given {@link RequestBody @RequestBody} annotation. + */ + protected Object resolveRequestBody(MethodParameter methodParam, NativeWebRequest webRequest, Object handler) + throws Exception { + + return readWithMessageConverters(methodParam, createHttpInputMessage(webRequest), methodParam.getParameterType()); + } + + private HttpEntity resolveHttpEntityRequest(MethodParameter methodParam, NativeWebRequest webRequest) + throws Exception { + + HttpInputMessage inputMessage = createHttpInputMessage(webRequest); + Class paramType = getHttpEntityType(methodParam); + Object body = readWithMessageConverters(methodParam, inputMessage, paramType); + return new HttpEntity(body, inputMessage.getHeaders()); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private Object readWithMessageConverters(MethodParameter methodParam, HttpInputMessage inputMessage, Class paramType) + throws Exception { + + MediaType contentType = inputMessage.getHeaders().getContentType(); + if (contentType == null) { + StringBuilder builder = new StringBuilder(ClassUtils.getShortName(methodParam.getParameterType())); + String paramName = methodParam.getParameterName(); + if (paramName != null) { + builder.append(' '); + builder.append(paramName); + } + throw new HttpMediaTypeNotSupportedException( + "Cannot extract parameter (" + builder.toString() + "): no Content-Type found"); + } + + List allSupportedMediaTypes = new ArrayList(); + if (this.messageConverters != null) { + for (HttpMessageConverter messageConverter : this.messageConverters) { + allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes()); + if (messageConverter.canRead(paramType, contentType)) { + if (logger.isDebugEnabled()) { + logger.debug("Reading [" + paramType.getName() + "] as \"" + contentType + +"\" using [" + messageConverter + "]"); + } + return messageConverter.read((Class) paramType, inputMessage); + } + } + } + throw new HttpMediaTypeNotSupportedException(contentType, allSupportedMediaTypes); + } + + private Class getHttpEntityType(MethodParameter methodParam) { + Assert.isAssignable(HttpEntity.class, methodParam.getParameterType()); + ParameterizedType type = (ParameterizedType) methodParam.getGenericParameterType(); + if (type.getActualTypeArguments().length == 1) { + Type typeArgument = type.getActualTypeArguments()[0]; + if (typeArgument instanceof Class) { + return (Class) typeArgument; + } + else if (typeArgument instanceof GenericArrayType) { + Type componentType = ((GenericArrayType) typeArgument).getGenericComponentType(); + if (componentType instanceof Class) { + // Surely, there should be a nicer way to do this + Object array = Array.newInstance((Class) componentType, 0); + return array.getClass(); + } + } + } + throw new IllegalArgumentException( + "HttpEntity parameter (" + methodParam.getParameterName() + ") is not parameterized"); + + } + + private Object resolveCookieValue(String cookieName, boolean required, String defaultValue, + MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall) + throws Exception { + + Class paramType = methodParam.getParameterType(); + if (cookieName.length() == 0) { + cookieName = getRequiredParameterName(methodParam); + } + Object cookieValue = resolveCookieValue(cookieName, paramType, webRequest); + if (cookieValue == null) { + if (defaultValue != null) { + cookieValue = resolveDefaultValue(defaultValue); + } + else if (required) { + raiseMissingCookieException(cookieName, paramType); + } + cookieValue = checkValue(cookieName, cookieValue, paramType); + } + WebDataBinder binder = createBinder(webRequest, null, cookieName); + initBinder(handlerForInitBinderCall, cookieName, binder, webRequest); + return binder.convertIfNecessary(cookieValue, paramType, methodParam); + } + + /** + * Resolves the given {@link CookieValue @CookieValue} annotation. + *

    Throws an UnsupportedOperationException by default. + */ + protected Object resolveCookieValue(String cookieName, Class paramType, NativeWebRequest webRequest) + throws Exception { + + throw new UnsupportedOperationException("@CookieValue not supported"); + } + + private Object resolvePathVariable(String pathVarName, MethodParameter methodParam, + NativeWebRequest webRequest, Object handlerForInitBinderCall) throws Exception { + + Class paramType = methodParam.getParameterType(); + if (pathVarName.length() == 0) { + pathVarName = getRequiredParameterName(methodParam); + } + String pathVarValue = resolvePathVariable(pathVarName, paramType, webRequest); + WebDataBinder binder = createBinder(webRequest, null, pathVarName); + initBinder(handlerForInitBinderCall, pathVarName, binder, webRequest); + return binder.convertIfNecessary(pathVarValue, paramType, methodParam); + } + + /** + * Resolves the given {@link PathVariable @PathVariable} annotation. + *

    Throws an UnsupportedOperationException by default. + */ + protected String resolvePathVariable(String pathVarName, Class paramType, NativeWebRequest webRequest) + throws Exception { + + throw new UnsupportedOperationException("@PathVariable not supported"); + } + + private String getRequiredParameterName(MethodParameter methodParam) { + String name = methodParam.getParameterName(); + if (name == null) { + throw new IllegalStateException( + "No parameter name specified for argument of type [" + methodParam.getParameterType().getName() + + "], and no parameter name information found in class file either."); + } + return name; + } + + private Object checkValue(String name, Object value, Class paramType) { + if (value == null) { + if (boolean.class == paramType) { + return Boolean.FALSE; + } + else if (paramType.isPrimitive()) { + throw new IllegalStateException("Optional " + paramType + " parameter '" + name + + "' is not present but cannot be translated into a null value due to being declared as a " + + "primitive type. Consider declaring it as object wrapper for the corresponding primitive type."); + } + } + return value; + } + + private WebDataBinder resolveModelAttribute(String attrName, MethodParameter methodParam, + ExtendedModelMap implicitModel, NativeWebRequest webRequest, Object handler) throws Exception { + + // Bind request parameter onto object... + String name = attrName; + if ("".equals(name)) { + name = Conventions.getVariableNameForParameter(methodParam); + } + Class paramType = methodParam.getParameterType(); + Object bindObject; + if (implicitModel.containsKey(name)) { + bindObject = implicitModel.get(name); + } + else if (this.methodResolver.isSessionAttribute(name, paramType)) { + bindObject = this.sessionAttributeStore.retrieveAttribute(webRequest, name); + if (bindObject == null) { + raiseSessionRequiredException("Session attribute '" + name + "' required - not found in session"); + } + } + else { + bindObject = BeanUtils.instantiateClass(paramType); + } + WebDataBinder binder = createBinder(webRequest, bindObject, name); + initBinder(handler, name, binder, webRequest); + return binder; + } + + + /** + * Determine whether the given value qualifies as a "binding candidate", i.e. might potentially be subject to + * bean-style data binding later on. + */ + protected boolean isBindingCandidate(Object value) { + return (value != null && !value.getClass().isArray() && !(value instanceof Collection) && + !(value instanceof Map) && !BeanUtils.isSimpleValueType(value.getClass())); + } + + protected void raiseMissingParameterException(String paramName, Class paramType) throws Exception { + throw new IllegalStateException("Missing parameter '" + paramName + "' of type [" + paramType.getName() + "]"); + } + + protected void raiseMissingHeaderException(String headerName, Class paramType) throws Exception { + throw new IllegalStateException("Missing header '" + headerName + "' of type [" + paramType.getName() + "]"); + } + + protected void raiseMissingCookieException(String cookieName, Class paramType) throws Exception { + throw new IllegalStateException( + "Missing cookie value '" + cookieName + "' of type [" + paramType.getName() + "]"); + } + + protected void raiseSessionRequiredException(String message) throws Exception { + throw new IllegalStateException(message); + } + + protected WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) + throws Exception { + + return new WebRequestDataBinder(target, objectName); + } + + private void doBind(WebDataBinder binder, NativeWebRequest webRequest, boolean validate, + Object[] validationHints, boolean failOnErrors) throws Exception { + + doBind(binder, webRequest); + if (validate) { + binder.validate(validationHints); + } + if (failOnErrors && binder.getBindingResult().hasErrors()) { + throw new BindException(binder.getBindingResult()); + } + } + + protected void doBind(WebDataBinder binder, NativeWebRequest webRequest) throws Exception { + ((WebRequestDataBinder) binder).bind(webRequest); + } + + /** + * Return a {@link HttpInputMessage} for the given {@link NativeWebRequest}. + *

    Throws an UnsupportedOperation1Exception by default. + */ + protected HttpInputMessage createHttpInputMessage(NativeWebRequest webRequest) throws Exception { + throw new UnsupportedOperationException("@RequestBody not supported"); + } + + /** + * Return a {@link HttpOutputMessage} for the given {@link NativeWebRequest}. + *

    Throws an UnsupportedOperationException by default. + */ + protected HttpOutputMessage createHttpOutputMessage(NativeWebRequest webRequest) throws Exception { + throw new UnsupportedOperationException("@Body not supported"); + } + + protected String parseDefaultValueAttribute(String value) { + return (ValueConstants.DEFAULT_NONE.equals(value) ? null : value); + } + + protected Object resolveDefaultValue(String value) { + return value; + } + + protected Object resolveCommonArgument(MethodParameter methodParameter, NativeWebRequest webRequest) + throws Exception { + + // Invoke custom argument resolvers if present... + if (this.customArgumentResolvers != null) { + for (WebArgumentResolver argumentResolver : this.customArgumentResolvers) { + Object value = argumentResolver.resolveArgument(methodParameter, webRequest); + if (value != WebArgumentResolver.UNRESOLVED) { + return value; + } + } + } + + // Resolution of standard parameter types... + Class paramType = methodParameter.getParameterType(); + Object value = resolveStandardArgument(paramType, webRequest); + if (value != WebArgumentResolver.UNRESOLVED && !ClassUtils.isAssignableValue(paramType, value)) { + throw new IllegalStateException("Standard argument type [" + paramType.getName() + + "] resolved to incompatible value of type [" + (value != null ? value.getClass() : null) + + "]. Consider declaring the argument type in a less specific fashion."); + } + return value; + } + + protected Object resolveStandardArgument(Class parameterType, NativeWebRequest webRequest) throws Exception { + if (WebRequest.class.isAssignableFrom(parameterType)) { + return webRequest; + } + return WebArgumentResolver.UNRESOLVED; + } + + protected final void addReturnValueAsModelAttribute(Method handlerMethod, Class handlerType, + Object returnValue, ExtendedModelMap implicitModel) { + + ModelAttribute attr = AnnotationUtils.findAnnotation(handlerMethod, ModelAttribute.class); + String attrName = (attr != null ? attr.value() : ""); + if ("".equals(attrName)) { + Class resolvedType = GenericTypeResolver.resolveReturnType(handlerMethod, handlerType); + attrName = Conventions.getVariableNameForReturnType(handlerMethod, resolvedType, returnValue); + } + implicitModel.addAttribute(attrName, returnValue); + } + +} Index: lams_common/src/java/org/lamsfoundation/lams/context/HandlerMethodResolver.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/context/HandlerMethodResolver.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/context/HandlerMethodResolver.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -0,0 +1,172 @@ +/* + * 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.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.core.BridgeMethodResolver; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; +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.SessionAttributes; + +/** + * Support class for resolving web method annotations in a handler type. + * Processes {@code @RequestMapping}, {@code @InitBinder}, + * {@code @ModelAttribute} and {@code @SessionAttributes}. + * + *

    Used by {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter} + * and {@link org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter}. + * + * @author Juergen Hoeller + * @since 2.5.2 + * @see org.springframework.web.bind.annotation.RequestMapping + * @see org.springframework.web.bind.annotation.InitBinder + * @see org.springframework.web.bind.annotation.ModelAttribute + * @see org.springframework.web.bind.annotation.SessionAttributes + * @deprecated as of 4.3, in favor of the {@code HandlerMethod}-based MVC infrastructure + */ +@Deprecated +public class HandlerMethodResolver { + + private final Set handlerMethods = new LinkedHashSet(); + + private final Set initBinderMethods = new LinkedHashSet(); + + private final Set modelAttributeMethods = new LinkedHashSet(); + + private RequestMapping typeLevelMapping; + + private boolean sessionAttributesFound; + + private final Set sessionAttributeNames = new HashSet(); + + private final Set> sessionAttributeTypes = new HashSet>(); + + private final Set actualSessionAttributeNames = + Collections.newSetFromMap(new ConcurrentHashMap(4)); + + + /** + * Initialize a new HandlerMethodResolver for the specified handler type. + * @param handlerType the handler class to introspect + */ + public void init(final Class handlerType) { + Set> handlerTypes = new LinkedHashSet>(); + Class specificHandlerType = null; + if (!Proxy.isProxyClass(handlerType)) { + handlerTypes.add(handlerType); + specificHandlerType = handlerType; + } + handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces())); + for (Class currentHandlerType : handlerTypes) { + final Class targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType); + ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() { + @Override + public void doWith(Method method) { + Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); + Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); + if (isHandlerMethod(specificMethod) && + (bridgedMethod == specificMethod || !isHandlerMethod(bridgedMethod))) { + handlerMethods.add(specificMethod); + } + else if (isInitBinderMethod(specificMethod) && + (bridgedMethod == specificMethod || !isInitBinderMethod(bridgedMethod))) { + initBinderMethods.add(specificMethod); + } + else if (isModelAttributeMethod(specificMethod) && + (bridgedMethod == specificMethod || !isModelAttributeMethod(bridgedMethod))) { + modelAttributeMethods.add(specificMethod); + } + } + }, ReflectionUtils.USER_DECLARED_METHODS); + } + this.typeLevelMapping = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class); + SessionAttributes sessionAttributes = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class); + this.sessionAttributesFound = (sessionAttributes != null); + if (this.sessionAttributesFound) { + this.sessionAttributeNames.addAll(Arrays.asList(sessionAttributes.names())); + this.sessionAttributeTypes.addAll(Arrays.asList(sessionAttributes.types())); + } + } + + protected boolean isHandlerMethod(Method method) { + return AnnotationUtils.findAnnotation(method, RequestMapping.class) != null; + } + + protected boolean isInitBinderMethod(Method method) { + return AnnotationUtils.findAnnotation(method, InitBinder.class) != null; + } + + protected boolean isModelAttributeMethod(Method method) { + return AnnotationUtils.findAnnotation(method, ModelAttribute.class) != null; + } + + + public final boolean hasHandlerMethods() { + return !this.handlerMethods.isEmpty(); + } + + public final Set getHandlerMethods() { + return this.handlerMethods; + } + + public final Set getInitBinderMethods() { + return this.initBinderMethods; + } + + public final Set getModelAttributeMethods() { + return this.modelAttributeMethods; + } + + public boolean hasTypeLevelMapping() { + return (this.typeLevelMapping != null); + } + + public RequestMapping getTypeLevelMapping() { + return this.typeLevelMapping; + } + + public boolean hasSessionAttributes() { + return this.sessionAttributesFound; + } + + public boolean isSessionAttribute(String attrName, Class attrType) { + if (this.sessionAttributeNames.contains(attrName) || this.sessionAttributeTypes.contains(attrType)) { + this.actualSessionAttributeNames.add(attrName); + return true; + } + else { + return false; + } + } + + public Set getActualSessionAttributeNames() { + return this.actualSessionAttributeNames; + } + +} Index: lams_common/src/java/org/lamsfoundation/lams/context/InternalPathMethodNameResolver.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/context/InternalPathMethodNameResolver.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/context/InternalPathMethodNameResolver.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -0,0 +1,129 @@ +/* + * 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.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Simple implementation of {@link MethodNameResolver} that maps URL to + * method name. Although this is the default implementation used by the + * {@link MultiActionController} class (because it requires no configuration), + * it's bit naive for most applications. In particular, we don't usually + * want to tie URL to implementation methods. + * + *

    + * Maps the resource name after the last slash, ignoring an extension. + * E.g. "/foo/bar/baz.html" to "baz", assuming a "/foo/bar/baz.html" + * controller mapping to the corresponding MultiActionController handler. + * method. Doesn't support wildcards. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @deprecated as of 4.3, in favor of annotation-driven handler methods + */ +@Deprecated +public class InternalPathMethodNameResolver extends AbstractUrlMethodNameResolver { + + private String prefix = ""; + + private String suffix = ""; + + /** Request URL path String --> method name String */ + private final Map methodNameCache = new ConcurrentHashMap<>(16); + + /** + * Specify a common prefix for handler method names. + * Will be prepended to the internal path found in the URL: + * e.g. internal path "baz", prefix "my" -> method name "mybaz". + */ + public void setPrefix(String prefix) { + this.prefix = (prefix != null ? prefix : ""); + } + + /** + * Return the common prefix for handler method names. + */ + protected String getPrefix() { + return this.prefix; + } + + /** + * Specify a common suffix for handler method names. + * Will be appended to the internal path found in the URL: + * e.g. internal path "baz", suffix "Handler" -> method name "bazHandler". + */ + public void setSuffix(String suffix) { + this.suffix = (suffix != null ? suffix : ""); + } + + /** + * Return the common suffix for handler method names. + */ + protected String getSuffix() { + return this.suffix; + } + + /** + * Extracts the method name indicated by the URL path. + * + * @see #extractHandlerMethodNameFromUrlPath + * @see #postProcessHandlerMethodName + */ + @Override + protected String getHandlerMethodNameForUrlPath(String urlPath) { + String methodName = this.methodNameCache.get(urlPath); + if (methodName == null) { + methodName = extractHandlerMethodNameFromUrlPath(urlPath); + methodName = postProcessHandlerMethodName(methodName); + this.methodNameCache.put(urlPath, methodName); + } + return methodName; + } + + /** + * Extract the handler method name from the given request URI. + * Delegates to {@code WebUtils.extractFilenameFromUrlPath(String)}. + * + * @param uri + * the request URI (e.g. "/index.html") + * @return the extracted URI filename (e.g. "index") + * @see org.springframework.web.util.WebUtils#extractFilenameFromUrlPath + */ + protected String extractHandlerMethodNameFromUrlPath(String uri) { + return WebUtils.extractFilenameFromUrlPath(uri); + } + + /** + * Build the full handler method name based on the given method name + * as indicated by the URL path. + *

    + * The default implementation simply applies prefix and suffix. + * This can be overridden, for example, to manipulate upper case + * / lower case, etc. + * + * @param methodName + * the original method name, as indicated by the URL path + * @return the full method name to use + * @see #getPrefix() + * @see #getSuffix() + */ + protected String postProcessHandlerMethodName(String methodName) { + return getPrefix() + methodName + getSuffix(); + } + +} Index: lams_common/src/java/org/lamsfoundation/lams/context/MethodNameResolver.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/context/MethodNameResolver.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/context/MethodNameResolver.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -0,0 +1,47 @@ +/* + * 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 javax.servlet.http.HttpServletRequest; + +/** + * Interface that parameterizes the MultiActionController class + * using the Strategy GoF Design pattern, allowing + * the mapping from incoming request to handler method name + * to be varied without affecting other application code. + * + *

    Illustrates how delegation can be more flexible than subclassing. + * + * @author Rod Johnson + * @see MultiActionController#setMethodNameResolver + * @deprecated as of 4.3, in favor of annotation-driven handler methods + */ +@Deprecated +public interface MethodNameResolver { + + /** + * Return a method name that can handle this request. Such + * mappings are typically, but not necessarily, based on URL. + * @param request current HTTP request + * @return a method name that can handle this request. + * Never returns {@code null}; throws exception if not resolvable. + * @throws NoSuchRequestHandlingMethodException if no handler method + * can be found for the given request + */ + String getHandlerMethodName(HttpServletRequest request) throws NoSuchRequestHandlingMethodException; + +} Index: lams_common/src/java/org/lamsfoundation/lams/context/NoSuchRequestHandlingMethodException.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/context/NoSuchRequestHandlingMethodException.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/context/NoSuchRequestHandlingMethodException.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -0,0 +1,87 @@ +/* + * 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.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.core.style.StylerUtils; +import org.springframework.web.util.UrlPathHelper; + +/** + * Exception thrown when there is no handler method ("action" method) + * for a specific HTTP request. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @see MethodNameResolver#getHandlerMethodName(javax.servlet.http.HttpServletRequest) + * @deprecated as of 4.3, in favor of annotation-driven handler methods + */ +@Deprecated +@SuppressWarnings("serial") +public class NoSuchRequestHandlingMethodException extends ServletException { + + private String methodName; + + /** + * Create a new NoSuchRequestHandlingMethodException for the given request. + * + * @param request + * the offending HTTP request + */ + public NoSuchRequestHandlingMethodException(HttpServletRequest request) { + this(new UrlPathHelper().getRequestUri(request), request.getMethod(), request.getParameterMap()); + } + + /** + * Create a new NoSuchRequestHandlingMethodException. + * + * @param urlPath + * the request URI that has been used for handler lookup + * @param method + * the HTTP request method of the request + * @param parameterMap + * the request's parameters as map + */ + public NoSuchRequestHandlingMethodException(String urlPath, String method, Map parameterMap) { + super("No matching handler method found for servlet request: path '" + urlPath + "', method '" + method + + "', parameters " + StylerUtils.style(parameterMap)); + } + + /** + * Create a new NoSuchRequestHandlingMethodException for the given request. + * + * @param methodName + * the name of the handler method that wasn't found + * @param controllerClass + * the class the handler method was expected to be in + */ + public NoSuchRequestHandlingMethodException(String methodName, Class controllerClass) { + super("No request handling method with name '" + methodName + "' in class [" + controllerClass.getName() + "]"); + this.methodName = methodName; + } + + /** + * Return the name of the offending method, if known. + */ + public String getMethodName() { + return this.methodName; + } + +} Index: lams_common/src/java/org/lamsfoundation/lams/context/ServletAnnotationMappingUtils.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/context/ServletAnnotationMappingUtils.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/context/ServletAnnotationMappingUtils.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -0,0 +1,166 @@ +/* + * Copyright 2002-2016 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.Iterator; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.http.MediaType; +import org.springframework.util.ObjectUtils; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.util.WebUtils; + +/** + * Helper class for annotation-based request mapping. + * + * @author Juergen Hoeller + * @author Arjen Poutsma + * @author Rossen Stoyanchev + * @since 2.5.2 + * @deprecated as of Spring 3.2, together with {@link DefaultAnnotationHandlerMapping}, + * {@link AnnotationMethodHandlerAdapter}, and {@link AnnotationMethodHandlerExceptionResolver}. + */ +@Deprecated +abstract class ServletAnnotationMappingUtils { + + /** + * Check whether the given request matches the specified request methods. + * + * @param methods + * the HTTP request methods to check against + * @param request + * the current HTTP request to check + */ + public static boolean checkRequestMethod(RequestMethod[] methods, HttpServletRequest request) { + String inputMethod = request.getMethod(); + if (ObjectUtils.isEmpty(methods) && !RequestMethod.OPTIONS.name().equals(inputMethod)) { + return true; + } + for (RequestMethod method : methods) { + if (method.name().equals(inputMethod)) { + return true; + } + } + return false; + } + + /** + * Check whether the given request matches the specified parameter conditions. + * + * @param params + * the parameter conditions, following + * {@link org.springframework.web.bind.annotation.RequestMapping#params() RequestMapping.#params()} + * @param request + * the current HTTP request to check + */ + public static boolean checkParameters(String[] params, HttpServletRequest request) { + if (!ObjectUtils.isEmpty(params)) { + for (String param : params) { + int separator = param.indexOf('='); + if (separator == -1) { + if (param.startsWith("!")) { + if (WebUtils.hasSubmitParameter(request, param.substring(1))) { + return false; + } + } else if (!WebUtils.hasSubmitParameter(request, param)) { + return false; + } + } else { + boolean negated = separator > 0 && param.charAt(separator - 1) == '!'; + String key = !negated ? param.substring(0, separator) : param.substring(0, separator - 1); + String value = param.substring(separator + 1); + boolean match = value.equals(request.getParameter(key)); + if (negated) { + match = !match; + } + if (!match) { + return false; + } + } + } + } + return true; + } + + /** + * Check whether the given request matches the specified header conditions. + * + * @param headers + * the header conditions, following + * {@link org.springframework.web.bind.annotation.RequestMapping#headers() RequestMapping.headers()} + * @param request + * the current HTTP request to check + */ + public static boolean checkHeaders(String[] headers, HttpServletRequest request) { + if (!ObjectUtils.isEmpty(headers)) { + for (String header : headers) { + int separator = header.indexOf('='); + if (separator == -1) { + if (header.startsWith("!")) { + if (request.getHeader(header.substring(1)) != null) { + return false; + } + } else if (request.getHeader(header) == null) { + return false; + } + } else { + boolean negated = (separator > 0 && header.charAt(separator - 1) == '!'); + String key = !negated ? header.substring(0, separator) : header.substring(0, separator - 1); + String value = header.substring(separator + 1); + if (ServletAnnotationMappingUtils.isMediaTypeHeader(key)) { + List requestMediaTypes = MediaType.parseMediaTypes(request.getHeader(key)); + List valueMediaTypes = MediaType.parseMediaTypes(value); + boolean found = false; + for (Iterator valIter = valueMediaTypes.iterator(); valIter.hasNext() && !found;) { + MediaType valueMediaType = valIter.next(); + for (Iterator reqIter = requestMediaTypes.iterator(); reqIter.hasNext() + && !found;) { + MediaType requestMediaType = reqIter.next(); + if (valueMediaType.includes(requestMediaType)) { + found = true; + } + } + + } + if (negated) { + found = !found; + } + if (!found) { + return false; + } + } else { + boolean match = value.equals(request.getHeader(key)); + if (negated) { + match = !match; + } + if (!match) { + return false; + } + } + } + } + } + return true; + } + + private static boolean isMediaTypeHeader(String headerName) { + return ("Accept".equalsIgnoreCase(headerName) || "Content-Type".equalsIgnoreCase(headerName)); + } + +} Index: lams_common/src/java/org/lamsfoundation/lams/context/SingletonBeanFactoryLocator.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/context/SingletonBeanFactoryLocator.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/context/SingletonBeanFactoryLocator.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -0,0 +1,562 @@ +/* + * 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.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.FatalBeanException; +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternUtils; + +/** + *

    + * Keyed-singleton implementation of {@link BeanFactoryLocator}, + * which accesses shared Spring {@link BeanFactory} instances. + *

    + * + *

    + * Please see the warning in BeanFactoryLocator's javadoc about appropriate usage + * of singleton style BeanFactoryLocator implementations. It is the opinion of the + * Spring team that the use of this class and similar classes is unnecessary except + * (sometimes) for a small amount of glue code. Excessive usage will lead to code + * that is more tightly coupled, and harder to modify or test. + *

    + * + *

    + * In this implementation, a BeanFactory is built up from one or more XML + * definition file fragments, accessed as resources. The default resource name + * searched for is 'classpath*:beanRefFactory.xml', with the Spring-standard + * 'classpath*:' prefix ensuring that if the classpath contains multiple copies + * of this file (perhaps one in each component jar) they will be combined. To + * override the default resource name, instead of using the no-arg + * {@link #getInstance()} method, use the {@link #getInstance(String selector)} + * variant, which will treat the 'selector' argument as the resource name to + * search for. + *

    + * + *

    + * The purpose of this 'outer' BeanFactory is to create and hold a copy of one + * or more 'inner' BeanFactory or ApplicationContext instances, and allow those + * to be obtained either directly or via an alias. As such, this class provides + * both singleton style access to one or more BeanFactories/ApplicationContexts, + * and also a level of indirection, allowing multiple pieces of code, which are + * not able to work in a Dependency Injection fashion, to refer to and use the + * same target BeanFactory/ApplicationContext instance(s), by different names. + *

    + * + *

    + * Consider an example application scenario: + * + *

      + *
    • {@code com.mycompany.myapp.util.applicationContext.xml} - + * ApplicationContext definition file which defines beans for 'util' layer. + *
    • {@code com.mycompany.myapp.dataaccess-applicationContext.xml} - + * ApplicationContext definition file which defines beans for 'data access' layer. + * Depends on the above. + *
    • {@code com.mycompany.myapp.services.applicationContext.xml} - + * ApplicationContext definition file which defines beans for 'services' layer. + * Depends on the above. + *
    + * + *

    + * In an ideal scenario, these would be combined to create one ApplicationContext, + * or created as three hierarchical ApplicationContexts, by one piece of code + * somewhere at application startup (perhaps a Servlet filter), from which all other + * code in the application would flow, obtained as beans from the context(s). However + * when third party code enters into the picture, things can get problematic. If the + * third party code needs to create user classes, which should normally be obtained + * from a Spring BeanFactory/ApplicationContext, but can handle only newInstance() + * style object creation, then some extra work is required to actually access and + * use object from a BeanFactory/ApplicationContext. One solutions is to make the + * class created by the third party code be just a stub or proxy, which gets the + * real object from a BeanFactory/ApplicationContext, and delegates to it. However, + * it is not normally workable for the stub to create the BeanFactory on each + * use, as depending on what is inside it, that can be an expensive operation. + * Additionally, there is a fairly tight coupling between the stub and the name of + * the definition resource for the BeanFactory/ApplicationContext. This is where + * SingletonBeanFactoryLocator comes in. The stub can obtain a + * SingletonBeanFactoryLocator instance, which is effectively a singleton, and + * ask it for an appropriate BeanFactory. A subsequent invocation (assuming the + * same class loader is involved) by the stub or another piece of code, will obtain + * the same instance. The simple aliasing mechanism allows the context to be asked + * for by a name which is appropriate for (or describes) the user. The deployer can + * match alias names to actual context names. + * + *

    + * Another use of SingletonBeanFactoryLocator, is to demand-load/use one or more + * BeanFactories/ApplicationContexts. Because the definition can contain one of more + * BeanFactories/ApplicationContexts, which can be independent or in a hierarchy, if + * they are set to lazy-initialize, they will only be created when actually requested + * for use. + * + *

    + * Given the above-mentioned three ApplicationContexts, consider the simplest + * SingletonBeanFactoryLocator usage scenario, where there is only one single + * {@code beanRefFactory.xml} definition file: + * + *

    + * <?xml version="1.0" encoding="UTF-8"?>
    + * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
    + *
    + * <beans>
    + *
    + *   <bean id="com.mycompany.myapp"
    + *         class="org.springframework.context.support.ClassPathXmlApplicationContext">
    + *     <constructor-arg>
    + *       <list>
    + *         <value>com/mycompany/myapp/util/applicationContext.xml</value>
    + *         <value>com/mycompany/myapp/dataaccess/applicationContext.xml</value>
    + *         <value>com/mycompany/myapp/dataaccess/services.xml</value>
    + *       </list>
    + *     </constructor-arg>
    + *   </bean>
    + *
    + * </beans>
    + * 
    + * + * The client code is as simple as: + * + *
    + * BeanFactoryLocator bfl = SingletonBeanFactoryLocator.getInstance();
    + * BeanFactoryReference bf = bfl.useBeanFactory("com.mycompany.myapp");
    + * // now use some bean from factory
    + * MyClass zed = bf.getFactory().getBean("mybean");
    + * 
    + * + * Another relatively simple variation of the {@code beanRefFactory.xml} definition file could be: + * + *
    + * <?xml version="1.0" encoding="UTF-8"?>
    + * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
    + *
    + * <beans>
    + *
    + *   <bean id="com.mycompany.myapp.util" lazy-init="true"
    + *         class="org.springframework.context.support.ClassPathXmlApplicationContext">
    + *     <constructor-arg>
    + *       <value>com/mycompany/myapp/util/applicationContext.xml</value>
    + *     </constructor-arg>
    + *   </bean>
    + *
    + *   <!-- child of above -->
    + *   <bean id="com.mycompany.myapp.dataaccess" lazy-init="true"
    + *         class="org.springframework.context.support.ClassPathXmlApplicationContext">
    + *     <constructor-arg>
    + *       <list><value>com/mycompany/myapp/dataaccess/applicationContext.xml</value></list>
    + *     </constructor-arg>
    + *     <constructor-arg>
    + *       <ref bean="com.mycompany.myapp.util"/>
    + *     </constructor-arg>
    + *   </bean>
    + *
    + *   <!-- child of above -->
    + *   <bean id="com.mycompany.myapp.services" lazy-init="true"
    + *         class="org.springframework.context.support.ClassPathXmlApplicationContext">
    + *     <constructor-arg>
    + *       <list><value>com/mycompany/myapp/dataaccess.services.xml</value></value>
    + *     </constructor-arg>
    + *     <constructor-arg>
    + *       <ref bean="com.mycompany.myapp.dataaccess"/>
    + *     </constructor-arg>
    + *   </bean>
    + *
    + *   <!-- define an alias -->
    + *   <bean id="com.mycompany.myapp.mypackage"
    + *         class="java.lang.String">
    + *     <constructor-arg>
    + *       <value>com.mycompany.myapp.services</value>
    + *     </constructor-arg>
    + *   </bean>
    + *
    + * </beans>
    + * 
    + * + *

    + * In this example, there is a hierarchy of three contexts created. The (potential) + * advantage is that if the lazy flag is set to true, a context will only be created + * if it's actually used. If there is some code that is only needed some of the time, + * this mechanism can save some resources. Additionally, an alias to the last context + * has been created. Aliases allow usage of the idiom where client code asks for a + * context with an id which represents the package or module the code is in, and the + * actual definition file(s) for the SingletonBeanFactoryLocator maps that id to + * a real context id. + * + *

    + * A final example is more complex, with a {@code beanRefFactory.xml} for every module. + * All the files are automatically combined to create the final definition. + * + *

    + * {@code beanRefFactory.xml} file inside jar for util module: + * + *

    + * <?xml version="1.0" encoding="UTF-8"?>
    + * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
    + *
    + * <beans>
    + *   <bean id="com.mycompany.myapp.util" lazy-init="true"
    + *        class="org.springframework.context.support.ClassPathXmlApplicationContext">
    + *     <constructor-arg>
    + *       <value>com/mycompany/myapp/util/applicationContext.xml</value>
    + *     </constructor-arg>
    + *   </bean>
    + * </beans>
    + * 
    + * + * {@code beanRefFactory.xml} file inside jar for data-access module:
    + * + *
    + * <?xml version="1.0" encoding="UTF-8"?>
    + * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
    + *
    + * <beans>
    + *   <!-- child of util -->
    + *   <bean id="com.mycompany.myapp.dataaccess" lazy-init="true"
    + *        class="org.springframework.context.support.ClassPathXmlApplicationContext">
    + *     <constructor-arg>
    + *       <list><value>com/mycompany/myapp/dataaccess/applicationContext.xml</value></list>
    + *     </constructor-arg>
    + *     <constructor-arg>
    + *       <ref bean="com.mycompany.myapp.util"/>
    + *     </constructor-arg>
    + *   </bean>
    + * </beans>
    + * 
    + * + * {@code beanRefFactory.xml} file inside jar for services module: + * + *
    + * <?xml version="1.0" encoding="UTF-8"?>
    + * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
    + *
    + * <beans>
    + *   <!-- child of data-access -->
    + *   <bean id="com.mycompany.myapp.services" lazy-init="true"
    + *        class="org.springframework.context.support.ClassPathXmlApplicationContext">
    + *     <constructor-arg>
    + *       <list><value>com/mycompany/myapp/dataaccess/services.xml</value></list>
    + *     </constructor-arg>
    + *     <constructor-arg>
    + *       <ref bean="com.mycompany.myapp.dataaccess"/>
    + *     </constructor-arg>
    + *   </bean>
    + * </beans>
    + * 
    + * + * {@code beanRefFactory.xml} file inside jar for mypackage module. This doesn't + * create any of its own contexts, but allows the other ones to be referred to be + * a name known to this module: + * + *
    + * <?xml version="1.0" encoding="UTF-8"?>
    + * <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
    + *
    + * <beans>
    + *   <!-- define an alias for "com.mycompany.myapp.services" -->
    + *   <alias name="com.mycompany.myapp.services" alias="com.mycompany.myapp.mypackage"/>
    + * </beans>
    + * 
    + * + * @author Colin Sampaleanu + * @author Juergen Hoeller + * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator + * @see org.springframework.context.access.DefaultLocatorFactory + */ +public class SingletonBeanFactoryLocator implements BeanFactoryLocator { + + private static final String DEFAULT_RESOURCE_LOCATION = "classpath*:beanRefFactory.xml"; + + protected static final Log logger = LogFactory.getLog(SingletonBeanFactoryLocator.class); + + /** The keyed BeanFactory instances */ + private static final Map instances = new HashMap<>(); + + /** + * Returns an instance which uses the default "classpath*:beanRefFactory.xml", + * as the name of the definition file(s). All resources returned by calling the + * current thread context ClassLoader's {@code getResources} method with + * this name will be combined to create a BeanFactory definition set. + * + * @return the corresponding BeanFactoryLocator instance + * @throws BeansException + * in case of factory loading failure + */ + public static BeanFactoryLocator getInstance() throws BeansException { + return SingletonBeanFactoryLocator.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 context + * ClassLoader'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 name 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 BeanFactory 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("SingletonBeanFactoryLocator.getInstance(): instances.hashCode=" + instances.hashCode() + + ", instances=" + instances); + } + BeanFactoryLocator bfl = instances.get(resourceLocation); + if (bfl == null) { + bfl = new SingletonBeanFactoryLocator(resourceLocation); + instances.put(resourceLocation, bfl); + } + return bfl; + } + } + + // We map BeanFactoryGroup objects by String keys, and by the definition object. + private final Map bfgInstancesByKey = new HashMap<>(); + + private final Map bfgInstancesByObj = new HashMap<>(); + + private final String resourceLocation; + + /** + * 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 SingletonBeanFactoryLocator(String resourceLocation) { + this.resourceLocation = resourceLocation; + } + + @Override + public BeanFactoryReference useBeanFactory(String factoryKey) throws BeansException { + synchronized (this.bfgInstancesByKey) { + BeanFactoryGroup bfg = this.bfgInstancesByKey.get(this.resourceLocation); + + if (bfg != null) { + bfg.refCount++; + } else { + // This group definition doesn't exist, we need to try to load it. + if (logger.isTraceEnabled()) { + logger.trace("Factory group with resource name [" + this.resourceLocation + + "] requested. Creating new instance."); + } + + // Create the BeanFactory but don't initialize it. + BeanFactory groupContext = createDefinition(this.resourceLocation, factoryKey); + + // Record its existence now, before instantiating any singletons. + bfg = new BeanFactoryGroup(); + bfg.definition = groupContext; + bfg.refCount = 1; + this.bfgInstancesByKey.put(this.resourceLocation, bfg); + this.bfgInstancesByObj.put(groupContext, bfg); + + // Now initialize the BeanFactory. This may cause a re-entrant invocation + // of this method, but since we've already added the BeanFactory to our + // mappings, the next time it will be found and simply have its + // reference count incremented. + try { + initializeDefinition(groupContext); + } catch (BeansException ex) { + this.bfgInstancesByKey.remove(this.resourceLocation); + this.bfgInstancesByObj.remove(groupContext); + throw new BootstrapException("Unable to initialize group definition. " + "Group resource name [" + + this.resourceLocation + "], factory key [" + factoryKey + "]", ex); + } + } + + try { + BeanFactory beanFactory; + if (factoryKey != null) { + beanFactory = bfg.definition.getBean(factoryKey, BeanFactory.class); + } else { + beanFactory = bfg.definition.getBean(BeanFactory.class); + } + return new CountingBeanFactoryReference(beanFactory, bfg.definition); + } catch (BeansException ex) { + throw new BootstrapException("Unable to return specified BeanFactory instance: factory key [" + + factoryKey + "], from group with resource name [" + this.resourceLocation + "]", ex); + } + + } + } + + /** + * Actually creates definition in the form of a BeanFactory, given a resource name + * which supports standard Spring resource prefixes ('classpath:', 'classpath*:', etc.) + * This is split out as a separate method so that subclasses can override the actual + * type used (to be an ApplicationContext, for example). + *

    + * The default implementation simply builds a + * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory} + * and populates it using an + * {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}. + *

    + * This method should not instantiate any singletons. That function is performed + * by {@link #initializeDefinition initializeDefinition()}, which should also be + * overridden if this method is. + * + * @param resourceLocation + * the resource location for this factory group + * @param factoryKey + * the bean name of the factory to obtain + * @return the corresponding BeanFactory reference + */ + protected BeanFactory createDefinition(String resourceLocation, String factoryKey) { + DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); + XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); + ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); + + try { + Resource[] configResources = resourcePatternResolver.getResources(resourceLocation); + if (configResources.length == 0) { + throw new FatalBeanException("Unable to find resource for specified definition. " + + "Group resource name [" + this.resourceLocation + "], factory key [" + factoryKey + "]"); + } + reader.loadBeanDefinitions(configResources); + } catch (IOException ex) { + throw new BeanDefinitionStoreException( + "Error accessing bean definition resource [" + this.resourceLocation + "]", ex); + } catch (BeanDefinitionStoreException ex) { + throw new FatalBeanException("Unable to load group definition: " + "group resource name [" + + this.resourceLocation + "], factory key [" + factoryKey + "]", ex); + } + + return factory; + } + + /** + * Instantiate singletons and do any other normal initialization of the factory. + * Subclasses that override {@link #createDefinition createDefinition()} should + * also override this method. + * + * @param groupDef + * the factory returned by {@link #createDefinition createDefinition()} + */ + protected void initializeDefinition(BeanFactory groupDef) { + if (groupDef instanceof ConfigurableListableBeanFactory) { + ((ConfigurableListableBeanFactory) groupDef).preInstantiateSingletons(); + } + } + + /** + * Destroy definition in separate method so subclass may work with other definition types. + * + * @param groupDef + * the factory returned by {@link #createDefinition createDefinition()} + * @param selector + * the resource location for this factory group + */ + protected void destroyDefinition(BeanFactory groupDef, String selector) { + if (groupDef instanceof ConfigurableBeanFactory) { + if (logger.isTraceEnabled()) { + logger.trace("Factory group with selector '" + selector + + "' being released, as there are no more references to it"); + } + ((ConfigurableBeanFactory) groupDef).destroySingletons(); + } + } + + /** + * We track BeanFactory instances with this class. + */ + private static class BeanFactoryGroup { + + private BeanFactory definition; + + private int refCount = 0; + } + + /** + * BeanFactoryReference implementation for this locator. + */ + private class CountingBeanFactoryReference implements BeanFactoryReference { + + private BeanFactory beanFactory; + + private BeanFactory groupContextRef; + + public CountingBeanFactoryReference(BeanFactory beanFactory, BeanFactory groupContext) { + this.beanFactory = beanFactory; + this.groupContextRef = groupContext; + } + + @Override + public BeanFactory getFactory() { + return this.beanFactory; + } + + // Note that it's legal to call release more than once! + @Override + public void release() throws FatalBeanException { + synchronized (bfgInstancesByKey) { + BeanFactory savedRef = this.groupContextRef; + if (savedRef != null) { + this.groupContextRef = null; + BeanFactoryGroup bfg = bfgInstancesByObj.get(savedRef); + if (bfg != null) { + bfg.refCount--; + if (bfg.refCount == 0) { + destroyDefinition(savedRef, resourceLocation); + bfgInstancesByKey.remove(resourceLocation); + bfgInstancesByObj.remove(savedRef); + } + } else { + // This should be impossible. + logger.warn("Tried to release a SingletonBeanFactoryLocator group definition " + + "more times than it has actually been used. Resource name [" + resourceLocation + + "]"); + } + } + } + } + } + +} Index: lams_common/src/java/org/lamsfoundation/lams/context/SourceHttpMessageConverter.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/context/SourceHttpMessageConverter.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/context/SourceHttpMessageConverter.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -0,0 +1,295 @@ +/* + * Copyright 2002-2016 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.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.StringReader; +import java.util.HashSet; +import java.util.Set; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLResolver; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stax.StAXSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.w3c.dom.Document; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.MediaType; +import org.springframework.http.converter.AbstractHttpMessageConverter; +import org.springframework.http.converter.HttpMessageConversionException; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.util.StreamUtils; + +/** + * Implementation of {@link org.springframework.http.converter.HttpMessageConverter} + * that can read and write {@link Source} objects. + * + * @author Arjen Poutsma + * @author Rossen Stoyanchev + * @since 3.0 + */ +public class SourceHttpMessageConverter extends AbstractHttpMessageConverter { + + private static final Set> SUPPORTED_CLASSES = new HashSet>(5); + + static { + SUPPORTED_CLASSES.add(DOMSource.class); + SUPPORTED_CLASSES.add(SAXSource.class); + SUPPORTED_CLASSES.add(StAXSource.class); + SUPPORTED_CLASSES.add(StreamSource.class); + SUPPORTED_CLASSES.add(Source.class); + } + + + private final TransformerFactory transformerFactory = TransformerFactory.newInstance(); + + private boolean supportDtd = false; + + private boolean processExternalEntities = false; + + + /** + * Sets the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes} + * to {@code text/xml} and {@code application/xml}, and {@code application/*-xml}. + */ + public SourceHttpMessageConverter() { + super(MediaType.APPLICATION_XML, MediaType.TEXT_XML, new MediaType("application", "*+xml")); + } + + + /** + * Indicates whether DTD parsing should be supported. + *

    Default is {@code false} meaning that DTD is disabled. + */ + public void setSupportDtd(boolean supportDtd) { + this.supportDtd = supportDtd; + } + + /** + * Whether DTD parsing is supported. + */ + public boolean isSupportDtd() { + return this.supportDtd; + } + + /** + * Indicates whether external XML entities are processed when converting to a Source. + *

    Default is {@code false}, meaning that external entities are not resolved. + *

    Note: setting this option to {@code true} also + * automatically sets {@link #setSupportDtd} to {@code true}. + */ + public void setProcessExternalEntities(boolean processExternalEntities) { + this.processExternalEntities = processExternalEntities; + if (processExternalEntities) { + setSupportDtd(true); + } + } + + /** + * Returns the configured value for whether XML external entities are allowed. + */ + public boolean isProcessExternalEntities() { + return this.processExternalEntities; + } + + + @Override + public boolean supports(Class clazz) { + return SUPPORTED_CLASSES.contains(clazz); + } + + @Override + @SuppressWarnings("unchecked") + protected T readInternal(Class clazz, HttpInputMessage inputMessage) + throws IOException, HttpMessageNotReadableException { + + InputStream body = inputMessage.getBody(); + if (DOMSource.class == clazz) { + return (T) readDOMSource(body); + } + else if (SAXSource.class == clazz) { + return (T) readSAXSource(body); + } + else if (StAXSource.class == clazz) { + return (T) readStAXSource(body); + } + else if (StreamSource.class == clazz || Source.class == clazz) { + return (T) readStreamSource(body); + } + else { + throw new HttpMessageConversionException("Could not read class [" + clazz + + "]. Only DOMSource, SAXSource, StAXSource, and StreamSource are supported."); + } + } + + private DOMSource readDOMSource(InputStream body) throws IOException { + try { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + documentBuilderFactory.setNamespaceAware(true); + documentBuilderFactory.setFeature( + "http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd()); + documentBuilderFactory.setFeature( + "http://xml.org/sax/features/external-general-entities", isProcessExternalEntities()); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + if (!isProcessExternalEntities()) { + documentBuilder.setEntityResolver(NO_OP_ENTITY_RESOLVER); + } + Document document = documentBuilder.parse(body); + return new DOMSource(document); + } + catch (NullPointerException ex) { + if (!isSupportDtd()) { + throw new HttpMessageNotReadableException("NPE while unmarshalling: " + + "This can happen due to the presence of DTD declarations which are disabled.", ex); + } + throw ex; + } + catch (ParserConfigurationException ex) { + throw new HttpMessageNotReadableException("Could not set feature: " + ex.getMessage(), ex); + } + catch (SAXException ex) { + throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex); + } + } + + private SAXSource readSAXSource(InputStream body) throws IOException { + try { + XMLReader xmlReader = XMLReaderFactory.createXMLReader(); + xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", !isSupportDtd()); + xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", isProcessExternalEntities()); + if (!isProcessExternalEntities()) { + xmlReader.setEntityResolver(NO_OP_ENTITY_RESOLVER); + } + byte[] bytes = StreamUtils.copyToByteArray(body); + return new SAXSource(xmlReader, new InputSource(new ByteArrayInputStream(bytes))); + } + catch (SAXException ex) { + throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex); + } + } + + private Source readStAXSource(InputStream body) { + try { + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, isSupportDtd()); + inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, isProcessExternalEntities()); + if (!isProcessExternalEntities()) { + inputFactory.setXMLResolver(NO_OP_XML_RESOLVER); + } + XMLStreamReader streamReader = inputFactory.createXMLStreamReader(body); + return new StAXSource(streamReader); + } + catch (XMLStreamException ex) { + throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex); + } + } + + private StreamSource readStreamSource(InputStream body) throws IOException { + byte[] bytes = StreamUtils.copyToByteArray(body); + return new StreamSource(new ByteArrayInputStream(bytes)); + } + + @Override + protected Long getContentLength(T t, MediaType contentType) { + if (t instanceof DOMSource) { + try { + CountingOutputStream os = new CountingOutputStream(); + transform(t, new StreamResult(os)); + return os.count; + } + catch (TransformerException ex) { + // ignore + } + } + return null; + } + + @Override + protected void writeInternal(T t, HttpOutputMessage outputMessage) + throws IOException, HttpMessageNotWritableException { + try { + Result result = new StreamResult(outputMessage.getBody()); + transform(t, result); + } + catch (TransformerException ex) { + throw new HttpMessageNotWritableException("Could not transform [" + t + "] to output message", ex); + } + } + + private void transform(Source source, Result result) throws TransformerException { + this.transformerFactory.newTransformer().transform(source, result); + } + + + private static class CountingOutputStream extends OutputStream { + + long count = 0; + + @Override + public void write(int b) throws IOException { + this.count++; + } + + @Override + public void write(byte[] b) throws IOException { + this.count += b.length; + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + this.count += len; + } + } + + + private static final EntityResolver NO_OP_ENTITY_RESOLVER = new EntityResolver() { + @Override + public InputSource resolveEntity(String publicId, String systemId) { + return new InputSource(new StringReader("")); + } + }; + + private static final XMLResolver NO_OP_XML_RESOLVER = new XMLResolver() { + @Override + public Object resolveEntity(String publicID, String systemID, String base, String ns) { + return StreamUtils.emptyInput(); + } + }; + +} Index: lams_common/src/java/org/lamsfoundation/lams/context/WebUtils.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/context/WebUtils.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/context/WebUtils.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -0,0 +1,51 @@ +package org.lamsfoundation.lams.context; + +import org.springframework.web.util.UriUtils; + +public class WebUtils { + + /** + * Extract the URL filename from the given request URL path. + * Correctly resolves nested paths such as "/products/view.html" as well. + * + * @param urlPath + * the request URL path (e.g. "/index.html") + * @return the extracted URI filename (e.g. "index") + * @deprecated as of Spring 4.3.2, in favor of custom code for such purposes + */ + @Deprecated + public static String extractFilenameFromUrlPath(String urlPath) { + String filename = WebUtils.extractFullFilenameFromUrlPath(urlPath); + int dotIndex = filename.lastIndexOf('.'); + if (dotIndex != -1) { + filename = filename.substring(0, dotIndex); + } + return filename; + } + + /** + * Extract the full URL filename (including file extension) from the given + * request URL path. Correctly resolve nested paths such as + * "/products/view.html" and remove any path and or query parameters. + * + * @param urlPath + * the request URL path (e.g. "/products/index.html") + * @return the extracted URI filename (e.g. "index.html") + * @deprecated as of Spring 4.3.2, in favor of custom code for such purposes + * (or {@link UriUtils#extractFileExtension} for the file extension use case) + */ + @Deprecated + public static String extractFullFilenameFromUrlPath(String urlPath) { + int end = urlPath.indexOf('?'); + if (end == -1) { + end = urlPath.indexOf('#'); + if (end == -1) { + end = urlPath.length(); + } + } + int begin = urlPath.lastIndexOf('/', end) + 1; + int paramIndex = urlPath.indexOf(';', begin); + end = (paramIndex != -1 && paramIndex < end ? paramIndex : end); + return urlPath.substring(begin, end); + } +} Index: lams_common/src/java/org/lamsfoundation/lams/context/XmlAwareFormHttpMessageConverter.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/context/XmlAwareFormHttpMessageConverter.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/context/XmlAwareFormHttpMessageConverter.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -0,0 +1,40 @@ +/* + * 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 javax.xml.transform.Source; + +import org.springframework.http.converter.FormHttpMessageConverter; + +/** + * Extension of {@link org.springframework.http.converter.FormHttpMessageConverter}, + * adding support for XML-based parts through a {@link SourceHttpMessageConverter}. + * + * @author Juergen Hoeller + * @since 3.0.3 + * @deprecated in favor of + * {@link org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter} + */ +@Deprecated +public class XmlAwareFormHttpMessageConverter extends FormHttpMessageConverter { + + public XmlAwareFormHttpMessageConverter() { + super(); + addPartConverter(new SourceHttpMessageConverter()); + } + +} Index: lams_common/src/java/org/lamsfoundation/lams/web/filter/LamsContextLoaderListener.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/web/filter/LamsContextLoaderListener.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/web/filter/LamsContextLoaderListener.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -0,0 +1,45 @@ +package org.lamsfoundation.lams.web.filter; + +import javax.servlet.ServletContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.lamsfoundation.lams.context.BeanFactoryLocator; +import org.lamsfoundation.lams.context.BeanFactoryReference; +import org.lamsfoundation.lams.context.ContextSingletonBeanFactoryLocator; +import org.springframework.context.ApplicationContext; +import org.springframework.web.context.ContextLoader; +import org.springframework.web.context.ContextLoaderListener; + +/** + * This class mirrors ContextLoaderListener from Spring 4 with BeanFactoryLocator mechanism enabled. + * + * @author Marcin Cieslak + */ +public class LamsContextLoaderListener extends ContextLoaderListener { + public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector"; + public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey"; + private BeanFactoryReference parentContextRef; + + @Override + protected ApplicationContext loadParentContext(ServletContext servletContext) { + ApplicationContext parentContext = null; + String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM); + String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM); + + if (parentContextKey != null) { + // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml" + BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector); + Log logger = LogFactory.getLog(ContextLoader.class); + if (logger.isDebugEnabled()) { + logger.debug("Getting parent context definition: using parent context key of '" + parentContextKey + + "' with BeanFactoryLocator"); + } + this.parentContextRef = locator.useBeanFactory(parentContextKey); + parentContext = (ApplicationContext) this.parentContextRef.getFactory(); + } + + return parentContext; + } + +} \ No newline at end of file Index: lams_common/src/java/org/lamsfoundation/lams/web/util/ApplicationContextUtil.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/web/util/ApplicationContextUtil.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/web/util/ApplicationContextUtil.java (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -0,0 +1,22 @@ +package org.lamsfoundation.lams.web.util; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +public class ApplicationContextUtil { + private static ApplicationContext parentContext = null; + + public static ApplicationContext getParentContext() { + if (parentContext == null) { + try { + ApplicationContext commonContext = new ClassPathXmlApplicationContext( + "/org/lamsfoundation/lams/beanRefContext.xml"); + parentContext = (ApplicationContext) commonContext.getBean("context.central"); + + } catch (Exception e) { + e.printStackTrace(); + } + } + return parentContext; + } +} Index: lams_gradebook/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_gradebook/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_gradebook/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -99,7 +99,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_learning/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_learning/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_learning/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -28,7 +28,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_monitoring/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_monitoring/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_monitoring/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -78,7 +78,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_assessment/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_assessment/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_tool_assessment/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -27,7 +27,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_bbb/web/WEB-INF/web.xml =================================================================== diff -u -rbf7188dd95898df53f786a38b116214d005c8fb2 -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_bbb/web/WEB-INF/web.xml (.../web.xml) (revision bf7188dd95898df53f786a38b116214d005c8fb2) +++ lams_tool_bbb/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -19,7 +19,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_chat/web/WEB-INF/web.xml =================================================================== diff -u -r92525f17be9db4e57a8551ff92d004f319fb4b73 -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_chat/web/WEB-INF/web.xml (.../web.xml) (revision 92525f17be9db4e57a8551ff92d004f319fb4b73) +++ lams_tool_chat/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -67,7 +67,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_daco/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_daco/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_tool_daco/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -27,7 +27,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_dimdim/web/WEB-INF/web.xml =================================================================== diff -u -rbf7188dd95898df53f786a38b116214d005c8fb2 -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_dimdim/web/WEB-INF/web.xml (.../web.xml) (revision bf7188dd95898df53f786a38b116214d005c8fb2) +++ lams_tool_dimdim/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -19,7 +19,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_doku/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_doku/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_tool_doku/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -27,7 +27,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_eadventure/web/WEB-INF/web.xml =================================================================== diff -u -rbf7188dd95898df53f786a38b116214d005c8fb2 -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_eadventure/web/WEB-INF/web.xml (.../web.xml) (revision bf7188dd95898df53f786a38b116214d005c8fb2) +++ lams_tool_eadventure/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -19,7 +19,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_forum/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_forum/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_tool_forum/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -83,7 +83,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_gmap/web/WEB-INF/web.xml =================================================================== diff -u -rbf7188dd95898df53f786a38b116214d005c8fb2 -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_gmap/web/WEB-INF/web.xml (.../web.xml) (revision bf7188dd95898df53f786a38b116214d005c8fb2) +++ lams_tool_gmap/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -19,7 +19,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_images/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_images/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_tool_images/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -27,7 +27,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_imscc/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_imscc/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_tool_imscc/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -27,7 +27,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_kaltura/web/WEB-INF/web.xml =================================================================== diff -u -rbf7188dd95898df53f786a38b116214d005c8fb2 -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_kaltura/web/WEB-INF/web.xml (.../web.xml) (revision bf7188dd95898df53f786a38b116214d005c8fb2) +++ lams_tool_kaltura/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -32,7 +32,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_lamc/web/WEB-INF/web.xml =================================================================== diff -u -r92525f17be9db4e57a8551ff92d004f319fb4b73 -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_lamc/web/WEB-INF/web.xml (.../web.xml) (revision 92525f17be9db4e57a8551ff92d004f319fb4b73) +++ lams_tool_lamc/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -79,7 +79,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_laqa/web/WEB-INF/web.xml =================================================================== diff -u -r92525f17be9db4e57a8551ff92d004f319fb4b73 -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_laqa/web/WEB-INF/web.xml (.../web.xml) (revision 92525f17be9db4e57a8551ff92d004f319fb4b73) +++ lams_tool_laqa/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -72,7 +72,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_larsrc/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_larsrc/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_tool_larsrc/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -27,7 +27,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_leader/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_leader/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_tool_leader/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -72,7 +72,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_mindmap/web/WEB-INF/web.xml =================================================================== diff -u -r92525f17be9db4e57a8551ff92d004f319fb4b73 -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_mindmap/web/WEB-INF/web.xml (.../web.xml) (revision 92525f17be9db4e57a8551ff92d004f319fb4b73) +++ lams_tool_mindmap/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -84,7 +84,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_nb/web/WEB-INF/web.xml =================================================================== diff -u -rbf6f5a84130fbab686b33d273b3323c45baaa21c -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_nb/web/WEB-INF/web.xml (.../web.xml) (revision bf6f5a84130fbab686b33d273b3323c45baaa21c) +++ lams_tool_nb/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -26,7 +26,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_notebook/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_notebook/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_tool_notebook/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -69,7 +69,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_pixlr/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_pixlr/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_tool_pixlr/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -67,7 +67,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_preview/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_preview/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_tool_preview/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -75,7 +75,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_sbmt/web/WEB-INF/web.xml =================================================================== diff -u -r92525f17be9db4e57a8551ff92d004f319fb4b73 -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_sbmt/web/WEB-INF/web.xml (.../web.xml) (revision 92525f17be9db4e57a8551ff92d004f319fb4b73) +++ lams_tool_sbmt/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -82,7 +82,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_scratchie/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_scratchie/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_tool_scratchie/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -27,7 +27,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_scribe/web/WEB-INF/web.xml =================================================================== diff -u -r92525f17be9db4e57a8551ff92d004f319fb4b73 -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_scribe/web/WEB-INF/web.xml (.../web.xml) (revision 92525f17be9db4e57a8551ff92d004f319fb4b73) +++ lams_tool_scribe/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -67,7 +67,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_spreadsheet/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_spreadsheet/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_tool_spreadsheet/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -27,7 +27,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_survey/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_survey/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_tool_survey/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -31,7 +31,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_task/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_task/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_tool_task/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -27,7 +27,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_videorecorder/web/WEB-INF/web.xml =================================================================== diff -u -rbf7188dd95898df53f786a38b116214d005c8fb2 -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_videorecorder/web/WEB-INF/web.xml (.../web.xml) (revision bf7188dd95898df53f786a38b116214d005c8fb2) +++ lams_tool_videorecorder/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -32,7 +32,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_vote/web/WEB-INF/web.xml =================================================================== diff -u -r92525f17be9db4e57a8551ff92d004f319fb4b73 -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_vote/web/WEB-INF/web.xml (.../web.xml) (revision 92525f17be9db4e57a8551ff92d004f319fb4b73) +++ lams_tool_vote/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -79,7 +79,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_whiteboard/web/WEB-INF/web.xml =================================================================== diff -u -r90e5fd15f6474eb56d0b497d1b73e14bda75e262 -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_whiteboard/web/WEB-INF/web.xml (.../web.xml) (revision 90e5fd15f6474eb56d0b497d1b73e14bda75e262) +++ lams_tool_whiteboard/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -27,7 +27,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_wiki/web/WEB-INF/web.xml =================================================================== diff -u -r92525f17be9db4e57a8551ff92d004f319fb4b73 -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_wiki/web/WEB-INF/web.xml (.../web.xml) (revision 92525f17be9db4e57a8551ff92d004f319fb4b73) +++ lams_tool_wiki/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -67,7 +67,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_wookie/web/WEB-INF/web.xml =================================================================== diff -u -rbf7188dd95898df53f786a38b116214d005c8fb2 -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_wookie/web/WEB-INF/web.xml (.../web.xml) (revision bf7188dd95898df53f786a38b116214d005c8fb2) +++ lams_tool_wookie/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -32,7 +32,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener Index: lams_tool_zoom/web/WEB-INF/web.xml =================================================================== diff -u -r2745b0e5dc858bbe667ca6f1710aaeb644626dfb -r22ae678dec4e0fb8e09b5624fc58e1e5f6890243 --- lams_tool_zoom/web/WEB-INF/web.xml (.../web.xml) (revision 2745b0e5dc858bbe667ca6f1710aaeb644626dfb) +++ lams_tool_zoom/web/WEB-INF/web.xml (.../web.xml) (revision 22ae678dec4e0fb8e09b5624fc58e1e5f6890243) @@ -77,7 +77,7 @@ - org.springframework.web.context.ContextLoaderListener + org.lamsfoundation.lams.web.filter.LamsContextLoaderListener