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}