001package io.jstach.opt.spring.webmvc;
002
003import java.io.IOException;
004import java.nio.charset.Charset;
005import java.nio.charset.StandardCharsets;
006import java.util.Map;
007
008import org.springframework.http.MediaType;
009import org.springframework.web.servlet.View;
010import org.springframework.web.servlet.view.RedirectView;
011
012import io.jstach.jstache.JStache;
013import io.jstach.jstache.JStacheInterfaces;
014import io.jstach.jstachio.JStachio;
015import io.jstach.jstachio.Output.CloseableEncodedOutput;
016import io.jstach.opt.spring.web.JStachioHttpMessageConverter;
017import jakarta.servlet.http.HttpServletRequest;
018import jakarta.servlet.http.HttpServletResponse;
019
020/**
021 * Another way to use JStachio with Spring MVC is to have models implement Springs
022 * {@link View} interface. You can enforce that your models implement this interface with
023 * {@link JStacheInterfaces}. Alternatively one can call {@link #of(Object)} on the model
024 * and return the result.
025 * <p>
026 * This view will by default use the static jstachio singleton and if configured correctly
027 * that will be the spring version.
028 * <p>
029 * This approach has pros and cons. It makes your models slightly coupled to Spring MVC
030 * but allows you to return different views if say you had to redirect on some inputs
031 * ({@link RedirectView}).
032 *
033 * @author agentgt
034 *
035 */
036public interface JStachioModelView extends View {
037
038        /**
039         * The default media type is "<code>text/html; charset=UTF-8</code>".
040         */
041        @SuppressWarnings("exports")
042        static final MediaType DEFAULT_MEDIA_TYPE = JStachioHttpMessageConverter.DEFAULT_MEDIA_TYPE;
043
044        /**
045         * The default buffer limit before bailing on trying to set
046         * <code>Content-Length</code>. The default is
047         * "{@value JStachioHttpMessageConverter#DEFAULT_BUFFER_LIMIT}".
048         */
049        static final int DEFAULT_BUFFER_LIMIT = JStachioHttpMessageConverter.DEFAULT_BUFFER_LIMIT;
050
051        @SuppressWarnings("exports")
052        @Override
053        default void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
054                        throws Exception {
055
056                String contentType = getContentType();
057                response.setContentType(contentType);
058                Charset charset = getMediaType().getCharset();
059                if (charset == null) {
060                        charset = StandardCharsets.UTF_8;
061                }
062                try (var o = createOutput(charset, response)) {
063                        jstachio().write(model(), o);
064                }
065        }
066
067        /**
068         * Creates the output from the servlet response to use for rendering.
069         * @param charset charset resolved from {@link #getMediaType()}
070         * @param response servlet response
071         * @return output that should be closed when finished
072         */
073        @SuppressWarnings("exports")
074        default CloseableEncodedOutput<IOException> createOutput(Charset charset, HttpServletResponse response) {
075                return new ServletThresholdEncodedOutput(charset, response, DEFAULT_BUFFER_LIMIT);
076        }
077
078        /**
079         * Returns the jstachio singleton by default.
080         * @return stachio singleton by default.
081         * @see JStachio#setStatic(java.util.function.Supplier)
082         */
083        default JStachio jstachio() {
084                return JStachio.of();
085        }
086
087        /**
088         * The model to be rendered by {@link #jstachio()}.
089         * @return model defaulting to <code>this</code> instance.
090         */
091        default Object model() {
092                return this;
093        }
094
095        @Override
096        default String getContentType() {
097                return getMediaType().toString();
098        }
099
100        /**
101         * The media type for this view. The default is
102         * "<code>text/html; charset=UTF-8</code>".
103         * @return the media type
104         */
105        @SuppressWarnings("exports")
106        default MediaType getMediaType() {
107                return DEFAULT_MEDIA_TYPE;
108        }
109
110        /**
111         * Creates a spring view from a model with content type:
112         * "<code>text/html; charset=UTF-8</code>".
113         * @param model an instance of a class annotated with {@link JStache}.
114         * @return view ready for rendering
115         */
116        public static JStachioModelView of(Object model) {
117                return of(model, MediaType.TEXT_HTML.toString());
118        }
119
120        /**
121         * Creates a spring view from a model.
122         * @param model an instance of a class annotated with {@link JStache}.
123         * @param contentType See {@link #getContentType()}
124         * @return view ready for rendering
125         */
126        public static JStachioModelView of(Object model, String contentType) {
127                MediaType mediaType = MediaType.parseMediaType(contentType);
128                return JStachioModelView.of(model, mediaType);
129        }
130
131        /**
132         * Creates a spring view from a model.
133         * @param model an instance of a class annotated with {@link JStache}.
134         * @param mediaType the mediaType
135         * @return view ready for rendering
136         */
137        static JStachioModelView of(Object model, @SuppressWarnings("exports") MediaType mediaType) {
138                /*
139                 * TODO potentially make this public on the next minor version release.
140                 */
141                return new JStachioModelView() {
142                        @Override
143                        public Object model() {
144                                return model;
145                        }
146
147                        @Override
148                        public MediaType getMediaType() {
149                                return mediaType;
150                        }
151                };
152        }
153
154}