1 package cn.home1.oss.lib.errorhandle.starter;
2
3 import static cn.home1.oss.boot.autoconfigure.AppErrorProperties.SearchStrategy.HIERARCHY_FIRST;
4 import static cn.home1.oss.lib.errorhandle.api.ExceptionTranslator.Location.HIERARCHY_FIRST_COMPARATOR;
5 import static cn.home1.oss.lib.errorhandle.api.ExceptionTranslator.Location.ORDER_FIRST_COMPARATOR;
6 import static cn.home1.oss.lib.errorhandle.api.ResolvedError.RESOLVED_ERROR_COOKIE;
7 import static com.google.common.base.Preconditions.checkNotNull;
8 import static com.google.common.collect.Lists.newArrayList;
9 import static cn.home1.oss.lib.common.Jackson2Utils.getJackson2Present;
10 import static java.util.concurrent.TimeUnit.DAYS;
11
12 import cn.home1.oss.boot.autoconfigure.AppErrorProperties;
13 import cn.home1.oss.boot.autoconfigure.AppProperties;
14 import cn.home1.oss.boot.autoconfigure.ConditionalOnNotEnvProduction;
15 import cn.home1.oss.lib.errorhandle.api.ExceptionTranslator;
16 import cn.home1.oss.lib.errorhandle.api.ExceptionTranslator.Location;
17 import cn.home1.oss.lib.errorhandle.api.ResolvedError;
18 import cn.home1.oss.lib.errorhandle.internal.BaseErrorController;
19 import cn.home1.oss.lib.errorhandle.internal.ContentCachingRequestFilter;
20 import cn.home1.oss.lib.errorhandle.internal.DefaultStackTraceIndicator;
21 import cn.home1.oss.lib.errorhandle.internal.ExtendedErrorAttributes;
22 import cn.home1.oss.lib.errorhandle.internal.rpc.FeignErrorDecoderConfiguration;
23 import cn.home1.oss.lib.errorhandle.internal.translator.DefaultExceptionTranslator;
24 import cn.home1.oss.lib.webmvc.internal.DefaultHttpEntityMethodProcessor;
25
26 import com.fasterxml.jackson.databind.ObjectMapper;
27 import cn.home1.oss.lib.common.msginterpolate.SpelMessageInterpolator;
28 import cn.home1.oss.lib.webmvc.api.DomainResolver;
29 import cn.home1.oss.lib.webmvc.api.JsonToken;
30 import cn.home1.oss.lib.webmvc.api.TokenBasedCookie;
31 import cn.home1.oss.lib.webmvc.api.TypeSafeCookie;
32 import cn.home1.oss.lib.webmvc.api.UrlEncodedToken;
33 import cn.home1.oss.lib.webmvc.starter.WebApplicationAutoConfiguration;
34
35 import lombok.extern.slf4j.Slf4j;
36
37 import org.apache.commons.lang3.StringUtils;
38 import org.springframework.beans.factory.annotation.Autowired;
39 import org.springframework.boot.autoconfigure.AutoConfigureAfter;
40 import org.springframework.boot.autoconfigure.AutoConfigureBefore;
41 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
42 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
43 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
44 import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
45 import org.springframework.boot.autoconfigure.condition.SearchStrategy;
46 import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
47 import org.springframework.boot.autoconfigure.web.ErrorAttributes;
48 import org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration;
49 import org.springframework.boot.autoconfigure.web.ServerProperties;
50 import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
51 import org.springframework.boot.context.properties.EnableConfigurationProperties;
52 import org.springframework.boot.web.servlet.ServletComponentScan;
53 import org.springframework.context.ApplicationContext;
54 import org.springframework.context.ApplicationContextAware;
55 import org.springframework.context.MessageSource;
56 import org.springframework.context.annotation.Bean;
57 import org.springframework.context.annotation.ComponentScan;
58 import org.springframework.context.annotation.Configuration;
59 import org.springframework.context.annotation.Import;
60 import org.springframework.context.support.ReloadableResourceBundleMessageSource;
61 import org.springframework.web.servlet.DispatcherServlet;
62 import org.springframework.web.servlet.HandlerExceptionResolver;
63 import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
64 import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
65 import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
66 import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
67 import org.springframework.web.servlet.mvc.method.annotation.JsonViewResponseBodyAdvice;
68 import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
69
70 import java.util.Comparator;
71 import java.util.List;
72 import java.util.Optional;
73
74 import javax.servlet.Servlet;
75
76
77
78
79
80
81
82
83 @AutoConfigureBefore({ErrorMvcAutoConfiguration.class, WebMvcAutoConfiguration.class, SecurityAutoConfiguration.class})
84 @AutoConfigureAfter({WebApplicationAutoConfiguration.class})
85 @ComponentScan(basePackages = {"cn.home1.oss.lib.errorhandle.starter"})
86 @ConditionalOnClass({Servlet.class, DispatcherServlet.class})
87 @ConditionalOnWebApplication
88 @Configuration
89 @EnableConfigurationProperties(value = {AppProperties.class, DefaultStackTraceIndicator.class})
90 @Import({
91 ExceptionResolverConfiguration.class,
92 FeignErrorDecoderConfiguration.class,
93 ExceptionHandlerConfiguration.class})
94 @ServletComponentScan(basePackages = {"cn.home1.oss.lib.errorhandle.filter"})
95 @Slf4j
96 public class ErrorHandleAutoConfiguration extends WebMvcConfigurerAdapter implements ApplicationContextAware {
97
98 private ApplicationContext applicationContext;
99
100 @Autowired
101 private AppProperties appProperties;
102
103 @Autowired
104 private DefaultHttpEntityMethodProcessor defaultHttpEntityMethodProcessor;
105
106 @Autowired
107 private DomainResolver domainResolver;
108 @Autowired
109 private ObjectMapper objectMapper;
110 @Autowired
111 private ServerProperties serverProperties;
112
113 @Bean
114 @ConditionalOnNotEnvProduction
115 public ContentCachingRequestFilter contentCachingRequestFilter() {
116 return new ContentCachingRequestFilter();
117 }
118
119 @Bean(name = RESOLVED_ERROR_COOKIE)
120 @ConditionalOnMissingBean(name = RESOLVED_ERROR_COOKIE)
121 public TypeSafeCookie<ResolvedError> resolvedErrorCookie() {
122 return buildResolvedErrorCookie(this.domainResolver, this.objectMapper);
123 }
124
125 @Bean
126 @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
127 public ExtendedErrorAttributes errorAttributes() {
128 return new ExtendedErrorAttributes();
129 }
130
131 @Bean
132
133 @ConditionalOnProperty(prefix = "app", name = "type", havingValue = "RESTFUL", matchIfMissing = false)
134 public BaseErrorController.RestfulErrorController restfulErrorController(
135 final ExtendedErrorAttributes errorAttributes) {
136 return new BaseErrorController.RestfulErrorController(errorAttributes, this.serverProperties.getError());
137 }
138
139 @Bean
140
141 @ConditionalOnProperty(prefix = "app", name = "type", havingValue = "TEMPLATE", matchIfMissing = false)
142 public BaseErrorController.TemplateErrorController templateErrorController(
143 final ExtendedErrorAttributes errorAttributes) {
144 return new BaseErrorController.TemplateErrorController(errorAttributes, this.serverProperties.getError());
145 }
146
147 @Bean
148
149 @ConditionalOnProperty(prefix = "app", name = "type", havingValue = "MIXED", matchIfMissing = true)
150 public BaseErrorController.MixedErrorController mixedErrorController(
151 final ExtendedErrorAttributes errorAttributes) {
152 return new BaseErrorController.MixedErrorController(errorAttributes, this.serverProperties.getError());
153 }
154
155 @Bean
156 public ExceptionTranslator exceptionTranslator() {
157 return buildExceptionTranslator(this.appProperties.getError().getSearchStrategy());
158 }
159
160 @Override
161 public void setApplicationContext(final ApplicationContext applicationContext) {
162 this.applicationContext = applicationContext;
163 }
164
165
166
167
168
169
170
171
172 @Override
173 public void configureHandlerExceptionResolvers(final List<HandlerExceptionResolver> resolvers) {
174
175 }
176
177
178
179
180
181
182 @Deprecated
183 protected final void addDefaultHandlerExceptionResolvers(
184 final List<HandlerExceptionResolver> resolvers
185 ) {
186 final List<ResponseBodyAdvice<?>> interceptors = newArrayList();
187 if (getJackson2Present()) {
188 interceptors.add(new JsonViewResponseBodyAdvice());
189 }
190 final ExceptionHandlerExceptionResolver handlerExceptionResolver = new ExceptionHandlerExceptionResolver();
191 handlerExceptionResolver.setMessageConverters(this.defaultHttpEntityMethodProcessor.getMessageConverters());
192 handlerExceptionResolver.setResponseBodyAdvice(interceptors);
193 handlerExceptionResolver.setApplicationContext(this.applicationContext);
194 handlerExceptionResolver.afterPropertiesSet();
195
196 final ResponseStatusExceptionResolver responseStatusExceptionResolver = new ResponseStatusExceptionResolver();
197 responseStatusExceptionResolver.setMessageSource(this.applicationContext);
198
199 resolvers.add(handlerExceptionResolver);
200
201
202 }
203
204 public static ExceptionTranslator buildExceptionTranslator(final AppErrorProperties.SearchStrategy searchStrategy) {
205 final Optional<MessageSource> messageSourceOptional = messageSource("classpath:/errorhandle/default");
206 final MessageSource defaultMessageSource = messageSourceOptional.isPresent() ? messageSourceOptional.get() : null;
207 checkNotNull(defaultMessageSource, "message source classpath:/errorhandle/default not present");
208
209 final Optional<MessageSource> applicationMessageSource = messageSource("classpath:/errorhandle/application");
210 final List<MessageSource> messageSources = applicationMessageSource.isPresent() ?
211 newArrayList(applicationMessageSource.get(), defaultMessageSource) :
212 newArrayList(defaultMessageSource);
213
214 final Comparator<Location> locationComparator = HIERARCHY_FIRST == searchStrategy ?
215 HIERARCHY_FIRST_COMPARATOR : ORDER_FIRST_COMPARATOR;
216
217 final DefaultExceptionTranslator defaultExceptionTranslator = new DefaultExceptionTranslator();
218 defaultExceptionTranslator.setLocationComparator(locationComparator);
219 defaultExceptionTranslator.setMessageSources(messageSources);
220 defaultExceptionTranslator.setMessageInterpolator(new SpelMessageInterpolator());
221 return defaultExceptionTranslator;
222 }
223
224 public static TypeSafeCookie<ResolvedError> buildResolvedErrorCookie(
225 final DomainResolver domainResolver, final ObjectMapper objectMapper) {
226 final JsonToken<ResolvedError> jsonToken = new JsonToken<>(ResolvedError.class, objectMapper);
227 final UrlEncodedToken<ResolvedError> urlEncodedToken = new UrlEncodedToken<>(jsonToken);
228 final int maxAge = (int) DAYS.toSeconds(1L);
229 return new TokenBasedCookie<>(domainResolver, false, maxAge, "resolved_error", false, urlEncodedToken);
230 }
231
232 static Optional<MessageSource> messageSource(final String location) {
233 final Optional<MessageSource> result;
234 if (StringUtils.isNotBlank(location)) {
235 final ReloadableResourceBundleMessageSource messages = new ReloadableResourceBundleMessageSource();
236 messages.setBasename(location);
237 messages.setDefaultEncoding("UTF-8");
238 messages.setFallbackToSystemLocale(false);
239 result = Optional.of(messages);
240 } else {
241 log.info("errorhandle messageSource not found at {}", location);
242 result = Optional.empty();
243 }
244 return result;
245 }
246 }