Java tutorial
/* * Copyright 2007-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.springbyexample.mvc.method.annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.lang.ArrayUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springbyexample.converter.ListConverter; import org.springbyexample.mvc.bind.annotation.RestRequestResource; import org.springbyexample.mvc.bind.annotation.RestResource; import org.springbyexample.mvc.converter.handler.ConverterHandlerInfo; import org.springbyexample.mvc.converter.handler.ConverterHandlerInterceptor; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.ApplicationContext; import org.springframework.core.BridgeMethodResolver; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils.MethodCallback; import org.springframework.util.ReflectionUtils.MethodFilter; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethodSelector; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.handler.MappedInterceptor; import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; /** * Creates {@link RequestMappingInfo} instances from type and method-level * {@link RequestMapping @RequestMapping} annotations in * {@link @RestResource @RestResource} classes. * * @author David Winterfeldt */ public class ServiceHandlerMapping extends RequestMappingHandlerMapping { final Logger logger = LoggerFactory.getLogger(getClass()); private List<String> basePackages; private RestServiceComponentProvider scanner; private ConverterHandlerInfo converterHandlerInfo; // temporary variable used to store the method // while registerHandlerMethod is called and overridden createHandlerMethod is called from it private Method restBridgedMethod; /** * Sets base packages to scan for <code>RestResource</code>s. */ public void setBasePackages(List<String> basePackages) { this.basePackages = basePackages; } /** * Sets scanner. */ public void setScanner(RestServiceComponentProvider scanner) { this.scanner = scanner; } /** * Sets converter handler info. */ public void setConverterHandlerInfo(ConverterHandlerInfo converterHandlerInfo) { Assert.notNull(converterHandlerInfo); Assert.notNull(converterHandlerInfo.getPropertyName()); this.converterHandlerInfo = converterHandlerInfo; } @Override protected void initHandlerMethods() { for (String basePackage : basePackages) { Collection<BeanDefinition> components = scanner.findCandidateComponents(basePackage); for (final BeanDefinition definition : components) { final Class<?> marshallingServiceClass; try { marshallingServiceClass = ClassUtils.forName(definition.getBeanClassName(), ClassUtils.getDefaultClassLoader()); } catch (ClassNotFoundException e) { throw new RuntimeException("Error loading interface class.", e); } catch (LinkageError e) { throw new RuntimeException("Error loading interface class.", e); } Set<Method> methods = HandlerMethodSelector.selectMethods(marshallingServiceClass, new MethodFilter() { @Override public boolean matches(Method method) { return (getMappingForMethod(method, marshallingServiceClass) != null); } }); for (Method method : methods) { registerHandler(marshallingServiceClass, method); } } } initInterceptors(); handlerMethodsInitialized(getHandlerMethods()); } /** * Register handler. */ private void registerHandler(Class<?> marshallingServiceClass, Method method) { ApplicationContext ctx = getApplicationContext(); RestResource restResource = AnnotationUtils.findAnnotation(marshallingServiceClass, RestResource.class); Class<?> serviceClass = restResource.service(); RestRequestResource restRequestResource = AnnotationUtils.findAnnotation(method, RestRequestResource.class); boolean export = (restRequestResource != null ? restRequestResource.export() : true); boolean relative = (restRequestResource != null ? restRequestResource.relative() : true); if (export) { Class<?> handlerServiceClass = serviceClass; String methodName = method.getName(); Class<?>[] paramTypes = method.getParameterTypes(); if (restRequestResource != null) { // explicit service specified if (restRequestResource.service() != ServiceValueConstants.DEFAULT_SERVICE_CLASS) { handlerServiceClass = restRequestResource.service(); } // explicit method name specified if (StringUtils.hasText(restRequestResource.methodName())) { methodName = restRequestResource.methodName(); } } Object handler = ctx.getBean(handlerServiceClass); Method serviceMethod = ClassUtils.getMethod(handlerServiceClass, methodName, paramTypes); RequestMappingInfo mapping = getMappingForMethod(method, marshallingServiceClass); if (relative) { List<String> patterns = new ArrayList<String>(); for (String pattern : mapping.getPatternsCondition().getPatterns()) { // add REST resource path prefix to URI, // if relative path is just '/' add an empty string patterns.add(restResource.path() + (!"/".equals(pattern) ? pattern : "")); } // create a new mapping based on the patterns (patterns are unmodifiable in existing RequestMappingInfo) mapping = new RequestMappingInfo( new PatternsRequestCondition(patterns.toArray(ArrayUtils.EMPTY_STRING_ARRAY), getUrlPathHelper(), getPathMatcher(), useSuffixPatternMatch(), useTrailingSlashMatch()), mapping.getMethodsCondition(), mapping.getParamsCondition(), mapping.getHeadersCondition(), mapping.getConsumesCondition(), mapping.getProducesCondition(), mapping.getCustomCondition()); } // need to set param types to use in createHandlerMethod before calling registerHandlerMethod restBridgedMethod = BridgeMethodResolver.findBridgedMethod(method); // mapping is key, HandlerMethod is value registerHandlerMethod(handler, serviceMethod, mapping); processConverters(restRequestResource, mapping, serviceMethod); } } @Override protected HandlerMethod createHandlerMethod(Object handler, Method method) { HandlerMethod handlerMethod = super.createHandlerMethod(handler, method); if (restBridgedMethod != null) { handlerMethod = new ServiceHandlerMethod(handler, method, restBridgedMethod); } return handlerMethod; } /** * Process and setup any converter handlers if one is configured on <code>RestRequestResource</code>. */ private void processConverters(RestRequestResource restRequestResource, RequestMappingInfo mapping, Method serviceMethod) { ApplicationContext ctx = getApplicationContext(); Class<?> converterClass = (restRequestResource != null ? restRequestResource.converter() : null); if (converterClass != null && converterClass != ServiceValueConstants.DEFAULT_CONVERTER_CLASS) { @SuppressWarnings("rawtypes") ListConverter converter = (ListConverter) ctx.getBean(converterClass); String[] pathPatterns = mapping.getPatternsCondition().getPatterns() .toArray(ArrayUtils.EMPTY_STRING_ARRAY); String methodSuffix = StringUtils.capitalize(converterHandlerInfo.getPropertyName()); String getterMethodName = "get" + methodSuffix; final String setterMethodName = "set" + methodSuffix; final Class<?> returnTypeClass = serviceMethod.getReturnType(); Method getResultsMethod = ReflectionUtils.findMethod(returnTypeClass, getterMethodName); final Class<?> resultReturnTypeClass = getResultsMethod.getReturnType(); Method setResultsMethod = ReflectionUtils.findMethod(returnTypeClass, setterMethodName, resultReturnTypeClass); final AtomicReference<Method> altSetResultsMethod = new AtomicReference<Method>(); // issue with ReflectionUtils, setterResultsMethod sometimes null from the command line (not getter?) if (setResultsMethod == null) { ReflectionUtils.doWithMethods(returnTypeClass, new MethodCallback() { @Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { if (setterMethodName.equals(method.getName())) { altSetResultsMethod.set(method); logger.debug( "Unable to use ReflectionUtils to find setter. returnTypeClass={} method={} resultReturnTypeClass={}", new Object[] { returnTypeClass, method, resultReturnTypeClass }); } } }); } HandlerInterceptor interceptor = new ConverterHandlerInterceptor(converter, returnTypeClass, getResultsMethod, (setResultsMethod != null ? setResultsMethod : altSetResultsMethod.get())); MappedInterceptor mappedInterceptor = new MappedInterceptor(pathPatterns, interceptor); setInterceptors(new Object[] { mappedInterceptor }); logger.info("Registered converter post handler for {} with {}.", pathPatterns, converterClass.getCanonicalName()); } } }