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 */ 028public class ViewSetupBeanPostProcessor implements BeanPostProcessor { 029 030 private final ApplicationContext context; 031 032 /** 033 * Constructor for Spring to inject the application context. 034 * @param context supplied by spring used to look for 035 * {@link JStachioModelViewConfigurer}s. 036 */ 037 public ViewSetupBeanPostProcessor(ApplicationContext context) { 038 this.context = context; 039 } 040 041 /** 042 * Look for a {@link ViewResolutionResultHandler} and replace it with a wrapper that 043 * configures {@link JStachioModelView} instance using the 044 * {@link JStachioModelViewConfigurer}s in the current context. 045 * @param bean the bean that is being created 046 * @param beanName the name of the bean 047 * @return Object a bean wrapped with {@link ViewSetupResultHandler} if needed 048 * @throws BeansException in case of errors 049 */ 050 @Override 051 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { 052 if (bean instanceof ViewResolutionResultHandler handler) { 053 return new ViewSetupResultHandler(this.context, handler); 054 } 055 return bean; 056 } 057 058 class ViewSetupResultHandler implements HandlerResultHandler, Ordered { 059 060 private final ViewResolutionResultHandler delegate; 061 062 private final List<JStachioModelViewConfigurer> configurers = new ArrayList<>(); 063 064 private final ApplicationContext context; 065 066 ViewSetupResultHandler(ApplicationContext context, ViewResolutionResultHandler handler) { 067 for (String name : context.getBeanNamesForType(JStachioModelViewConfigurer.class)) { 068 this.configurers.add((JStachioModelViewConfigurer) context.getBean(name)); 069 } 070 this.delegate = handler; 071 this.context = context; 072 } 073 074 @Override 075 public int getOrder() { 076 return this.delegate.getOrder(); 077 } 078 079 @Override 080 public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) { 081 JStachioModelView view = findView(result.getReturnValue()); 082 if (view != null) { 083 for (JStachioModelViewConfigurer configurer : configurers) { 084 configurer.configure(view.model(), result.getModel().asMap(), exchange); 085 } 086 } 087 return this.delegate.handleResult(exchange, result); 088 } 089 090 protected RequestContext createRequestContext(ServerWebExchange exchange, Map<String, Object> model) { 091 return new RequestContext(exchange, model, this.context, getRequestDataValueProcessor()); 092 } 093 094 @Nullable 095 protected RequestDataValueProcessor getRequestDataValueProcessor() { 096 ApplicationContext context = this.context; 097 if (context != null && context.containsBean(AbstractView.REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME)) { 098 return context.getBean(AbstractView.REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME, 099 RequestDataValueProcessor.class); 100 } 101 return null; 102 } 103 104 private JStachioModelView findView(Object view) { 105 if (view != null) { 106 if (view instanceof Rendering rendering) { 107 view = rendering.view(); 108 } 109 if (view instanceof JStachioModelView jview) { 110 return jview; 111 } 112 if (view instanceof String viewName) { 113 if (this.context.getBean(viewName) instanceof JStachioModelView jview) { 114 return jview; 115 } 116 } 117 } 118 return null; 119 } 120 121 @Override 122 public boolean supports(HandlerResult result) { 123 return this.delegate.supports(result); 124 } 125 126 } 127 128}