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