001package io.jstach.opt.spring.webflux; 002 003import java.util.ArrayList; 004import java.util.List; 005import java.util.Map; 006 007import org.springframework.beans.BeansException; 008import org.springframework.beans.factory.config.BeanPostProcessor; 009import org.springframework.context.ApplicationContext; 010import org.springframework.core.Ordered; 011import org.springframework.lang.Nullable; 012import org.springframework.web.reactive.HandlerResult; 013import org.springframework.web.reactive.HandlerResultHandler; 014import org.springframework.web.reactive.result.view.AbstractView; 015import org.springframework.web.reactive.result.view.Rendering; 016import org.springframework.web.reactive.result.view.RequestContext; 017import org.springframework.web.reactive.result.view.RequestDataValueProcessor; 018import org.springframework.web.reactive.result.view.ViewResolutionResultHandler; 019import org.springframework.web.server.ServerWebExchange; 020 021import reactor.core.publisher.Mono; 022 023/** 024 * A {@link BeanPostProcessor} that it registers a {@link HandlerResultHandler} that 025 * automatically applies all {@link JStachioModelViewConfigurer} instances to views before 026 * rendering. 027 */ 028@SuppressWarnings("exports") 029public class ViewSetupBeanPostProcessor implements BeanPostProcessor { 030 031 private final ApplicationContext context; 032 033 /** 034 * Constructor for Spring to inject the application context. 035 * @param context supplied by spring used to look for 036 * {@link JStachioModelViewConfigurer}s. 037 */ 038 public ViewSetupBeanPostProcessor(ApplicationContext context) { 039 this.context = context; 040 } 041 042 /** 043 * Look for a {@link ViewResolutionResultHandler} and replace it with a wrapper that 044 * configures {@link JStachioModelView} instance using the 045 * {@link JStachioModelViewConfigurer}s in the current context. 046 * @param bean the bean that is being created 047 * @param beanName the name of the bean 048 * @return Object a bean wrapped with {@link ViewSetupResultHandler} if needed 049 * @throws BeansException in case of errors 050 */ 051 @Override 052 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { 053 if (bean instanceof ViewResolutionResultHandler handler) { 054 return new ViewSetupResultHandler(this.context, handler); 055 } 056 return bean; 057 } 058 059 class ViewSetupResultHandler implements HandlerResultHandler, Ordered { 060 061 private final ViewResolutionResultHandler delegate; 062 063 private final List<JStachioModelViewConfigurer> configurers = new ArrayList<>(); 064 065 private final ApplicationContext context; 066 067 ViewSetupResultHandler(ApplicationContext context, ViewResolutionResultHandler handler) { 068 for (String name : context.getBeanNamesForType(JStachioModelViewConfigurer.class)) { 069 this.configurers.add((JStachioModelViewConfigurer) context.getBean(name)); 070 } 071 this.delegate = handler; 072 this.context = context; 073 } 074 075 @Override 076 public int getOrder() { 077 return this.delegate.getOrder(); 078 } 079 080 @Override 081 public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) { 082 JStachioModelView view = findView(result.getReturnValue()); 083 if (view != null) { 084 for (JStachioModelViewConfigurer configurer : configurers) { 085 configurer.configure(view.model(), result.getModel().asMap(), exchange); 086 } 087 } 088 return this.delegate.handleResult(exchange, result); 089 } 090 091 protected RequestContext createRequestContext(ServerWebExchange exchange, Map<String, Object> model) { 092 return new RequestContext(exchange, model, this.context, getRequestDataValueProcessor()); 093 } 094 095 @Nullable 096 protected RequestDataValueProcessor getRequestDataValueProcessor() { 097 ApplicationContext context = this.context; 098 if (context != null && context.containsBean(AbstractView.REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME)) { 099 return context.getBean(AbstractView.REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME, 100 RequestDataValueProcessor.class); 101 } 102 return null; 103 } 104 105 private JStachioModelView findView(Object view) { 106 if (view != null) { 107 if (view instanceof Rendering rendering) { 108 view = rendering.view(); 109 } 110 if (view instanceof JStachioModelView jview) { 111 return jview; 112 } 113 if (view instanceof String viewName) { 114 if (this.context.getBean(viewName) instanceof JStachioModelView jview) { 115 return jview; 116 } 117 } 118 } 119 return null; 120 } 121 122 @Override 123 public boolean supports(HandlerResult result) { 124 return this.delegate.supports(result); 125 } 126 127 } 128 129}