001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.method.annotation; 018 019import java.util.Collections; 020import java.util.List; 021 022import org.springframework.lang.Nullable; 023import org.springframework.util.Assert; 024import org.springframework.util.ObjectUtils; 025import org.springframework.web.bind.WebDataBinder; 026import org.springframework.web.bind.annotation.InitBinder; 027import org.springframework.web.bind.support.DefaultDataBinderFactory; 028import org.springframework.web.bind.support.WebBindingInitializer; 029import org.springframework.web.context.request.NativeWebRequest; 030import org.springframework.web.method.HandlerMethod; 031import org.springframework.web.method.support.InvocableHandlerMethod; 032 033/** 034 * Adds initialization to a WebDataBinder via {@code @InitBinder} methods. 035 * 036 * @author Rossen Stoyanchev 037 * @since 3.1 038 */ 039public class InitBinderDataBinderFactory extends DefaultDataBinderFactory { 040 041 private final List<InvocableHandlerMethod> binderMethods; 042 043 044 /** 045 * Create a new InitBinderDataBinderFactory instance. 046 * @param binderMethods {@code @InitBinder} methods 047 * @param initializer for global data binder initialization 048 */ 049 public InitBinderDataBinderFactory(@Nullable List<InvocableHandlerMethod> binderMethods, 050 @Nullable WebBindingInitializer initializer) { 051 052 super(initializer); 053 this.binderMethods = (binderMethods != null ? binderMethods : Collections.emptyList()); 054 } 055 056 057 /** 058 * Initialize a WebDataBinder with {@code @InitBinder} methods. 059 * <p>If the {@code @InitBinder} annotation specifies attributes names, 060 * it is invoked only if the names include the target object name. 061 * @throws Exception if one of the invoked @{@link InitBinder} methods fails 062 * @see #isBinderMethodApplicable 063 */ 064 @Override 065 public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception { 066 for (InvocableHandlerMethod binderMethod : this.binderMethods) { 067 if (isBinderMethodApplicable(binderMethod, dataBinder)) { 068 Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder); 069 if (returnValue != null) { 070 throw new IllegalStateException( 071 "@InitBinder methods must not return a value (should be void): " + binderMethod); 072 } 073 } 074 } 075 } 076 077 /** 078 * Determine whether the given {@code @InitBinder} method should be used 079 * to initialize the given {@link WebDataBinder} instance. By default we 080 * check the specified attribute names in the annotation value, if any. 081 */ 082 protected boolean isBinderMethodApplicable(HandlerMethod initBinderMethod, WebDataBinder dataBinder) { 083 InitBinder ann = initBinderMethod.getMethodAnnotation(InitBinder.class); 084 Assert.state(ann != null, "No InitBinder annotation"); 085 String[] names = ann.value(); 086 return (ObjectUtils.isEmpty(names) || ObjectUtils.containsElement(names, dataBinder.getObjectName())); 087 } 088 089}