11/*
22 * JBoss, Home of Professional Open Source.
3- * Copyright 2014 Red Hat, Inc., and individual contributors
3+ * Copyright 2025 Red Hat, Inc., and individual contributors
44 * as indicated by the @author tags.
55 *
66 * Licensed under the Apache License, Version 2.0 (the "License");
2020import io .undertow .server .HttpHandler ;
2121import io .undertow .server .HttpServerExchange ;
2222import io .undertow .util .AttachmentKey ;
23- import io .undertow .util .PathTemplate ;
24- import io .undertow .util .PathTemplateMatcher ;
25-
23+ import io .undertow .util .PathTemplateParser ;
24+ import io .undertow .util .PathTemplateRouter ;
25+ import io .undertow .util .PathTemplateRouteResult ;
26+ import io .undertow .util .PathTemplateRouterFactory ;
27+ import java .util .function .Supplier ;
28+ import java .util .ArrayList ;
29+ import java .util .List ;
2630import java .util .Map ;
27- import java .util .Set ;
31+ import java .util .Objects ;
2832import java .util .stream .Collectors ;
2933
3034/**
31- * A handler that matches URI templates
35+ * A drop-in substitute for the old PathTemplateHandler class. Ideally, one should use {@link PathTemplateRouterHandler} by
36+ * instantiating it with a {@link PathTemplateRouterFactory}. This class implements all of the methods from the original
37+ * PathTemplateHandler to provide backwards compatibility.
3238 *
33- * @author Stuart Douglas
34- * @see PathTemplateMatcher
39+ * @author Dirk Roets. This class was originally written by Stuart Douglas. After the introduction of
40+ * {@link PathTemplateRouterFactory}, it was rewritten against the original interface and tests.
3541 */
3642public class PathTemplateHandler implements HttpHandler {
3743
38- private final boolean rewriteQueryParameters ;
39-
40- private final HttpHandler next ;
41-
4244 /**
4345 * @see io.undertow.util.PathTemplateMatch#ATTACHMENT_KEY
4446 */
4547 @ Deprecated
46- public static final AttachmentKey <PathTemplateMatch > PATH_TEMPLATE_MATCH = AttachmentKey .create (PathTemplateMatch .class );
48+ public static final AttachmentKey <PathTemplateHandler .PathTemplateMatch > PATH_TEMPLATE_MATCH = AttachmentKey .
49+ create (PathTemplateHandler .PathTemplateMatch .class );
4750
48- private final PathTemplateMatcher <HttpHandler > pathTemplateMatcher = new PathTemplateMatcher <>();
51+ private final boolean rewriteQueryParameters ;
52+ private final PathTemplateRouterFactory .SimpleBuilder <HttpHandler > builder ;
53+ private final Object lock = new Object ();
54+ private volatile PathTemplateRouter <HttpHandler > router ;
4955
56+ /**
57+ * Default constructor. Uses {@link ResponseCodeHandler#HANDLE_404} as the next (default) handler and sets
58+ * 'rewriteQueryParameters' to 'true'.
59+ */
5060 public PathTemplateHandler () {
5161 this (true );
5262 }
5363
54- public PathTemplateHandler (boolean rewriteQueryParameters ) {
64+ /**
65+ * Uses {@link ResponseCodeHandler#HANDLE_404} as the next (default) handler.
66+ *
67+ * @param rewriteQueryParameters Path parameters that are returned by the underlying router will be added as query
68+ * parameters to the exchange if this flag is 'true'.
69+ */
70+ public PathTemplateHandler (final boolean rewriteQueryParameters ) {
5571 this (ResponseCodeHandler .HANDLE_404 , rewriteQueryParameters );
5672 }
5773
58- public PathTemplateHandler (HttpHandler next ) {
74+ /**
75+ * Sets 'rewriteQueryParameters' to 'true'.
76+ *
77+ * @param next The next (default) handler to use when requests do not match any of the specified templates.
78+ */
79+ public PathTemplateHandler (final HttpHandler next ) {
5980 this (next , true );
6081 }
6182
62- public PathTemplateHandler (HttpHandler next , boolean rewriteQueryParameters ) {
83+ /**
84+ * @param next The next (default) handler to use when requests do not match any of the specified templates.
85+ * @param rewriteQueryParameters Path parameters that are returned by the underlying router will be added as query
86+ * parameters to the exchange if this flag is 'true'.
87+ */
88+ public PathTemplateHandler (final HttpHandler next , final boolean rewriteQueryParameters ) {
89+ Objects .requireNonNull (next );
90+
6391 this .rewriteQueryParameters = rewriteQueryParameters ;
64- this .next = next ;
92+ builder = PathTemplateRouterFactory .SimpleBuilder .newBuilder (next );
93+ router = builder .build ();
6594 }
6695
67- @ Override
68- public void handleRequest (HttpServerExchange exchange ) throws Exception {
69- PathTemplateMatcher .PathMatchResult <HttpHandler > match = pathTemplateMatcher .match (exchange .getRelativePath ());
70- if (match == null ) {
71- next .handleRequest (exchange );
72- return ;
73- }
74- exchange .putAttachment (PATH_TEMPLATE_MATCH , new PathTemplateMatch (match .getMatchedTemplate (), match .getParameters ()));
75- exchange .putAttachment (io .undertow .util .PathTemplateMatch .ATTACHMENT_KEY , new io .undertow .util .PathTemplateMatch (match .getMatchedTemplate (), match .getParameters ()));
76- if (rewriteQueryParameters ) {
77- for (Map .Entry <String , String > entry : match .getParameters ().entrySet ()) {
78- exchange .addQueryParam (entry .getKey (), entry .getValue ());
79- }
96+ /**
97+ * Adds a template and handler to the underlying router.
98+ *
99+ * @param uriTemplate The URI path template.
100+ * @param handler The handler to use for requests that match the specified template.
101+ *
102+ * @return Reference to this handler.
103+ */
104+ public PathTemplateHandler add (final String uriTemplate , final HttpHandler handler ) {
105+ Objects .requireNonNull (uriTemplate );
106+ Objects .requireNonNull (handler );
107+
108+ // PathTemplateRouter builders are not thread-safe, so we need to synchronize.
109+ synchronized (lock ) {
110+ builder .addTemplate (uriTemplate , handler );
111+ router = builder .build ();
80112 }
81- match .getValue ().handleRequest (exchange );
82- }
83113
84- public PathTemplateHandler add (final String uriTemplate , final HttpHandler handler ) {
85- pathTemplateMatcher .add (uriTemplate , handler );
86114 return this ;
87115 }
88116
117+ /**
118+ * Removes a template from the underlying router.
119+ *
120+ * @param uriTemplate The URI path template.
121+ *
122+ * @return Reference to this handler.
123+ */
89124 public PathTemplateHandler remove (final String uriTemplate ) {
90- pathTemplateMatcher .remove (uriTemplate );
125+ Objects .requireNonNull (uriTemplate );
126+
127+ // PathTemplateRouter builders are not thread-safe, so we need to synchronize.
128+ synchronized (lock ) {
129+ builder .removeTemplate (uriTemplate );
130+ router = builder .build ();
131+ }
132+
91133 return this ;
92134 }
93135
94136 @ Override
95137 public String toString () {
96- Set <PathTemplate > paths = pathTemplateMatcher .getPathTemplates ();
97- if (paths .size () == 1 ) {
98- return "path-template( " + paths .toArray ()[0 ] + " )" ;
138+ final List <PathTemplateParser .PathTemplatePatternEqualsAdapter <PathTemplateParser .PathTemplate <Supplier <HttpHandler >>>> templates
139+ = new ArrayList <>(builder .getBuilder ().getTemplates ().keySet ());
140+
141+ final StringBuilder sb = new StringBuilder ();
142+ sb .append ("path-template( " );
143+ if (templates .size () == 1 ) {
144+ sb .append (templates .get (0 ).getPattern ().getPathTemplate ()).append (" )" );
99145 } else {
100- return "path-template( {" + paths .stream ().map (s -> s .getTemplateString ().toString ()).collect (Collectors .joining (", " )) + "} )" ;
146+ sb .append ('{' ).append (
147+ // Creates a ", " separated string of all patterns in this handler.
148+ templates .stream ().map (s -> s .getPattern ().getPathTemplate ()).collect (Collectors .joining (", " ))
149+ ).append ("} )" );
150+ }
151+ return sb .toString ();
152+ }
153+
154+ @ Override
155+ public void handleRequest (final HttpServerExchange exchange ) throws Exception {
156+ final PathTemplateRouteResult <HttpHandler > routeResult = router .route (exchange .getRelativePath ());
157+ if (routeResult .getPathTemplate ().isEmpty ()) {
158+ // This is the default handler, therefore it doesn't contain path parameters.
159+ routeResult .getTarget ().handleRequest (exchange );
160+ return ;
101161 }
162+
163+ exchange .putAttachment (PATH_TEMPLATE_MATCH , new PathTemplateMatch (routeResult ));
164+ exchange .putAttachment (io .undertow .util .PathTemplateMatch .ATTACHMENT_KEY , routeResult );
165+ if (rewriteQueryParameters ) {
166+ for (Map .Entry <String , String > entry : routeResult .getParameters ().entrySet ()) {
167+ exchange .addQueryParam (entry .getKey (), entry .getValue ());
168+ }
169+ }
170+ routeResult .getTarget ().handleRequest (exchange );
102171 }
103172
104173 /**
@@ -107,20 +176,26 @@ public String toString() {
107176 @ Deprecated
108177 public static final class PathTemplateMatch {
109178
110- private final String matchedTemplate ;
111- private final Map <String , String > parameters ;
179+ private final io .undertow .util .PathTemplateMatch pathTemplateMatch ;
180+
181+ PathTemplateMatch (
182+ final io .undertow .util .PathTemplateMatch pathTemplateMatch
183+ ) {
184+ this .pathTemplateMatch = Objects .requireNonNull (pathTemplateMatch );
185+ }
112186
113- public PathTemplateMatch (String matchedTemplate , Map <String , String > parameters ) {
114- this .matchedTemplate = matchedTemplate ;
115- this .parameters = parameters ;
187+ public PathTemplateMatch (final String matchedTemplate , final Map <String , String > parameters ) {
188+ this (
189+ new io .undertow .util .PathTemplateMatch (matchedTemplate , parameters )
190+ );
116191 }
117192
118193 public String getMatchedTemplate () {
119- return matchedTemplate ;
194+ return pathTemplateMatch . getMatchedTemplate () ;
120195 }
121196
122197 public Map <String , String > getParameters () {
123- return parameters ;
198+ return pathTemplateMatch . getParameters () ;
124199 }
125200 }
126201}
0 commit comments