001/* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014package org.gbif.ws.server.processor; 015 016import org.gbif.api.annotation.ParamName; 017 018import java.lang.reflect.Field; 019import java.lang.reflect.Method; 020import java.util.Arrays; 021import java.util.HashMap; 022import java.util.Map; 023import java.util.concurrent.ConcurrentHashMap; 024 025import org.springframework.beans.BeanUtils; 026import org.springframework.beans.factory.annotation.Autowired; 027import org.springframework.core.MethodParameter; 028import org.springframework.web.bind.WebDataBinder; 029import org.springframework.web.bind.annotation.RequestParam; 030import org.springframework.web.context.request.NativeWebRequest; 031import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; 032import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor; 033 034/** Process {@link ParamName}. */ 035public class ParamNameProcessor extends ServletModelAttributeMethodProcessor { 036 037 @Autowired private RequestMappingHandlerAdapter requestMappingHandlerAdapter; 038 039 private static final Map<Class<?>, Map<String, String>> PARAM_MAPPINGS_CACHE = 040 new ConcurrentHashMap<>(256); 041 private static final Map<Class<?>, Map<String, String>> METHODS_MAPPINGS_CACHE = 042 new ConcurrentHashMap<>(256); 043 044 public ParamNameProcessor() { 045 super(false); 046 } 047 048 @Override 049 public boolean supportsParameter(MethodParameter parameter) { 050 return parameter.hasParameterAnnotation(RequestParam.class) 051 && !BeanUtils.isSimpleProperty(parameter.getParameterType()) 052 && (Arrays.stream(parameter.getParameterType().getDeclaredMethods()) 053 .anyMatch(method -> method.getAnnotation(ParamName.class) != null) 054 || Arrays.stream(parameter.getParameterType().getDeclaredFields()) 055 .anyMatch(field -> field.getAnnotation(ParamName.class) != null)); 056 } 057 058 @Override 059 protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest nativeWebRequest) { 060 Object target = binder.getTarget(); 061 Map<String, String> paramMappings = this.getParamMappings(target.getClass()); 062 Map<String, String> methodMappings = this.getMethodMappings(target.getClass()); 063 ParamNameDataBinder paramNameDataBinder = 064 new ParamNameDataBinder(target, binder.getObjectName(), paramMappings, methodMappings); 065 requestMappingHandlerAdapter.getWebBindingInitializer().initBinder(paramNameDataBinder); 066 super.bindRequestParameters(paramNameDataBinder, nativeWebRequest); 067 } 068 069 /** 070 * Get param mappings. It creates a simple mapping: parameter name -> field name. Cache param 071 * mappings in memory. 072 */ 073 private Map<String, String> getParamMappings(Class<?> targetClass) { 074 // first check cache 075 if (PARAM_MAPPINGS_CACHE.containsKey(targetClass)) { 076 return PARAM_MAPPINGS_CACHE.get(targetClass); 077 } 078 079 Map<String, String> paramMappings = new HashMap<>(32); 080 081 // process fields 082 Field[] fields = targetClass.getDeclaredFields(); 083 for (Field field : fields) { 084 ParamName paramName = field.getAnnotation(ParamName.class); 085 if (paramName != null && !paramName.value().isEmpty()) { 086 paramMappings.put(paramName.value(), field.getName()); 087 } 088 } 089 090 // put them to cache 091 PARAM_MAPPINGS_CACHE.put(targetClass, paramMappings); 092 return paramMappings; 093 } 094 095 /** 096 * Get param mappings. It creates a simple mapping: parameter name -> method name. Cache param 097 * mappings in memory. 098 */ 099 private Map<String, String> getMethodMappings(Class<?> targetClass) { 100 // first check cache 101 if (METHODS_MAPPINGS_CACHE.containsKey(targetClass)) { 102 return METHODS_MAPPINGS_CACHE.get(targetClass); 103 } 104 105 Map<String, String> methodMappings = new HashMap<>(32); 106 107 // process methods 108 final Method[] methods = targetClass.getDeclaredMethods(); 109 for (Method method : methods) { 110 final ParamName paramName = method.getAnnotation(ParamName.class); 111 if (paramName != null && !paramName.value().isEmpty()) { 112 methodMappings.put(paramName.value(), method.getName()); 113 } 114 } 115 116 // put them to cache 117 METHODS_MAPPINGS_CACHE.put(targetClass, methodMappings); 118 return methodMappings; 119 } 120}