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}