44import com .fasterxml .jackson .databind .ObjectMapper ;
55import jakarta .servlet .http .HttpServletRequest ;
66import jakarta .servlet .http .HttpServletResponse ;
7- import org .springframework .core .annotation .AnnotatedElementUtils ;
87import org .springframework .http .HttpHeaders ;
98import org .springframework .web .method .HandlerMethod ;
109import org .springframework .web .servlet .HandlerInterceptor ;
11- import org .springframework .web .servlet .ModelAndView ;
1210
1311import java .lang .reflect .Method ;
14- import java .time .Duration ;
1512import java .util .Collection ;
1613import java .util .HashMap ;
1714import java .util .stream .Collectors ;
1815
19- import static io .github .wimdeblauwe .htmx .spring .boot .mvc .HtmxResponseHeader .*;
20-
2116/**
2217 * HandlerInterceptor that adds htmx specific headers to the response.
2318 */
2419public class HtmxHandlerInterceptor implements HandlerInterceptor {
2520
2621 private final ObjectMapper objectMapper ;
22+ private final HtmxHandlerMethodAnnotationHandler handlerMethodAnnotationHandler ;
2723
28- public HtmxHandlerInterceptor (ObjectMapper objectMapper ) {
24+ public HtmxHandlerInterceptor (ObjectMapper objectMapper , HtmxHandlerMethodAnnotationHandler handlerMethodAnnotationHandler ) {
2925 this .objectMapper = objectMapper ;
26+ this .handlerMethodAnnotationHandler = handlerMethodAnnotationHandler ;
3027 }
3128
3229 @ Override
@@ -78,18 +75,8 @@ public boolean preHandle(HttpServletRequest request,
7875
7976 if (handler instanceof HandlerMethod ) {
8077 Method method = ((HandlerMethod ) handler ).getMethod ();
81- setHxLocation (request , response , method );
82- setHxPushUrl (request , response , method );
83- setHxRedirect (request , response , method );
84- setHxReplaceUrl (request , response , method );
85- setHxReswap (response , method );
86- setHxRetarget (response , method );
87- setHxReselect (response , method );
88- setHxTrigger (response , method );
89- setHxTriggerAfterSettle (response , method );
90- setHxTriggerAfterSwap (response , method );
91- setHxRefresh (response , method );
9278 setVary (request , response );
79+ handlerMethodAnnotationHandler .handleMethod (method , request , response );
9380 }
9481
9582 return true ;
@@ -101,110 +88,6 @@ private void setVary(HttpServletRequest request, HttpServletResponse response) {
10188 }
10289 }
10390
104- private void setHxLocation (HttpServletRequest request , HttpServletResponse response , Method method ) {
105- HxLocation methodAnnotation = AnnotatedElementUtils .findMergedAnnotation (method , HxLocation .class );
106- if (methodAnnotation != null ) {
107- var location = convertToLocation (methodAnnotation );
108- if (location .hasContextData ()) {
109- location .setPath (RequestContextUtils .createUrl (request , location .getPath (), methodAnnotation .contextRelative ()));
110- setHeaderJsonValue (response , HtmxResponseHeader .HX_LOCATION , location );
111- } else {
112- setHeader (response , HtmxResponseHeader .HX_LOCATION , RequestContextUtils .createUrl (request , location .getPath (), methodAnnotation .contextRelative ()));
113- }
114- }
115- }
116-
117- private void setHxPushUrl (HttpServletRequest request , HttpServletResponse response , Method method ) {
118- HxPushUrl methodAnnotation = AnnotatedElementUtils .findMergedAnnotation (method , HxPushUrl .class );
119- if (methodAnnotation != null ) {
120- if (HtmxValue .TRUE .equals (methodAnnotation .value ())) {
121- setHeader (response , HX_PUSH_URL , getRequestUrl (request ));
122- } else {
123- setHeader (response , HX_PUSH_URL , RequestContextUtils .createUrl (request , methodAnnotation .value (), methodAnnotation .contextRelative ()));
124- }
125- }
126- }
127-
128- private void setHxRedirect (HttpServletRequest request , HttpServletResponse response , Method method ) {
129- HxRedirect methodAnnotation = AnnotatedElementUtils .findMergedAnnotation (method , HxRedirect .class );
130- if (methodAnnotation != null ) {
131- setHeader (response , HX_REDIRECT , RequestContextUtils .createUrl (request , methodAnnotation .value (), methodAnnotation .contextRelative ()));
132- }
133- }
134-
135- private void setHxReplaceUrl (HttpServletRequest request , HttpServletResponse response , Method method ) {
136- HxReplaceUrl methodAnnotation = AnnotatedElementUtils .findMergedAnnotation (method , HxReplaceUrl .class );
137- if (methodAnnotation != null ) {
138- if (HtmxValue .TRUE .equals (methodAnnotation .value ())) {
139- setHeader (response , HX_REPLACE_URL , getRequestUrl (request ));
140- } else {
141- setHeader (response , HX_REPLACE_URL , RequestContextUtils .createUrl (request , methodAnnotation .value (), methodAnnotation .contextRelative ()));
142- }
143- }
144- }
145-
146- private void setHxReswap (HttpServletResponse response , Method method ) {
147- HxReswap methodAnnotation = AnnotatedElementUtils .findMergedAnnotation (method , HxReswap .class );
148- if (methodAnnotation != null ) {
149- setHeader (response , HX_RESWAP , convertToReswap (methodAnnotation ));
150- }
151- }
152-
153- private void setHxRetarget (HttpServletResponse response , Method method ) {
154- HxRetarget methodAnnotation = AnnotatedElementUtils .findMergedAnnotation (method , HxRetarget .class );
155- if (methodAnnotation != null ) {
156- setHeader (response , HX_RETARGET , methodAnnotation .value ());
157- }
158- }
159-
160- private void setHxReselect (HttpServletResponse response , Method method ) {
161- HxReselect methodAnnotation = AnnotatedElementUtils .findMergedAnnotation (method , HxReselect .class );
162- if (methodAnnotation != null ) {
163- setHeader (response , HX_RESELECT , methodAnnotation .value ());
164- }
165- }
166-
167- private void setHxTrigger (HttpServletResponse response , Method method ) {
168- HxTrigger methodAnnotation = AnnotatedElementUtils .findMergedAnnotation (method , HxTrigger .class );
169- if (methodAnnotation != null ) {
170- setHeader (response , convertToHeader (methodAnnotation .lifecycle ()), methodAnnotation .value ());
171- }
172- }
173-
174- private void setHxTriggerAfterSettle (HttpServletResponse response , Method method ) {
175- HxTriggerAfterSettle methodAnnotation = AnnotatedElementUtils .findMergedAnnotation (method , HxTriggerAfterSettle .class );
176- if (methodAnnotation != null ) {
177- setHeader (response , HtmxResponseHeader .HX_TRIGGER_AFTER_SETTLE , methodAnnotation .value ());
178- }
179- }
180-
181- private void setHxTriggerAfterSwap (HttpServletResponse response , Method method ) {
182- HxTriggerAfterSwap methodAnnotation = AnnotatedElementUtils .findMergedAnnotation (method , HxTriggerAfterSwap .class );
183- if (methodAnnotation != null ) {
184- setHeader (response , HtmxResponseHeader .HX_TRIGGER_AFTER_SWAP , methodAnnotation .value ());
185- }
186- }
187-
188- private void setHxRefresh (HttpServletResponse response , Method method ) {
189- HxRefresh methodAnnotation = AnnotatedElementUtils .findMergedAnnotation (method , HxRefresh .class );
190- if (methodAnnotation != null ) {
191- setHeader (response , HX_REFRESH , HtmxValue .TRUE );
192- }
193- }
194-
195- private HtmxResponseHeader convertToHeader (HxTriggerLifecycle lifecycle ) {
196- switch (lifecycle ) {
197- case RECEIVE :
198- return HX_TRIGGER ;
199- case SETTLE :
200- return HX_TRIGGER_AFTER_SETTLE ;
201- case SWAP :
202- return HX_TRIGGER_AFTER_SWAP ;
203- default :
204- throw new IllegalArgumentException ("Unknown lifecycle:" + lifecycle );
205- }
206- }
207-
20891 private void setHeaderJsonValue (HttpServletResponse response , HtmxResponseHeader header , Object value ) {
20992 try {
21093 response .setHeader (header .getValue (), objectMapper .writeValueAsString (value ));
@@ -213,87 +96,6 @@ private void setHeaderJsonValue(HttpServletResponse response, HtmxResponseHeader
21396 }
21497 }
21598
216- private void setHeader (HttpServletResponse response , HtmxResponseHeader header , String value ) {
217- response .setHeader (header .getValue (), value );
218- }
219-
220- private void setHeader (HttpServletResponse response , HtmxResponseHeader header , String [] values ) {
221- response .setHeader (header .getValue (), String .join ("," , values ));
222- }
223-
224- private HtmxLocation convertToLocation (HxLocation annotation ) {
225- var location = new HtmxLocation ();
226- location .setPath (annotation .path ());
227- if (!annotation .source ().isEmpty ()) {
228- location .setSource (annotation .source ());
229- }
230- if (!annotation .event ().isEmpty ()) {
231- location .setEvent (annotation .event ());
232- }
233- if (!annotation .handler ().isEmpty ()) {
234- location .setHandler (annotation .handler ());
235- }
236- if (!annotation .target ().isEmpty ()) {
237- location .setTarget (annotation .target ());
238- }
239- if (!annotation .target ().isEmpty ()) {
240- location .setSwap (annotation .swap ());
241- }
242- if (!annotation .select ().isEmpty ()) {
243- location .setSelect (annotation .select ());
244- }
245- return location ;
246- }
247-
248- private String convertToReswap (HxReswap annotation ) {
249-
250- var reswap = new HtmxReswap (annotation .value ());
251- if (annotation .swap () != -1 ) {
252- reswap .swap (Duration .ofMillis (annotation .swap ()));
253- }
254- if (annotation .settle () != -1 ) {
255- reswap .swap (Duration .ofMillis (annotation .settle ()));
256- }
257- if (annotation .transition ()) {
258- reswap .transition ();
259- }
260- if (annotation .focusScroll () != HxReswap .FocusScroll .UNDEFINED ) {
261- reswap .focusScroll (annotation .focusScroll () == HxReswap .FocusScroll .TRUE );
262- }
263- if (annotation .show () != HxReswap .Position .UNDEFINED ) {
264- reswap .show (convertToPosition (annotation .show ()));
265- if (!annotation .showTarget ().isEmpty ()) {
266- reswap .scrollTarget (annotation .showTarget ());
267- }
268- }
269- if (annotation .scroll () != HxReswap .Position .UNDEFINED ) {
270- reswap .scroll (convertToPosition (annotation .scroll ()));
271- if (!annotation .scrollTarget ().isEmpty ()) {
272- reswap .scrollTarget (annotation .scrollTarget ());
273- }
274- }
275-
276- return reswap .toString ();
277- }
278-
279- private HtmxReswap .Position convertToPosition (HxReswap .Position position ) {
280- return switch (position ) {
281- case TOP -> HtmxReswap .Position .TOP ;
282- case BOTTOM -> HtmxReswap .Position .BOTTOM ;
283- default -> throw new IllegalStateException ("Unexpected value: " + position );
284- };
285- }
286-
287- private String getRequestUrl (HttpServletRequest request ) {
288- String path = request .getRequestURI ();
289- String queryString = request .getQueryString ();
290-
291- if (queryString != null && !queryString .isEmpty ()) {
292- path += "?" + queryString ;
293- }
294- return path ;
295- }
296-
29799 private void addHxTriggerHeaders (HttpServletResponse response , HtmxResponseHeader headerName , Collection <HtmxTrigger > triggers ) {
298100 if (triggers .isEmpty ()) {
299101 return ;
0 commit comments