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}