PathTemplateHandler.java
/*
* JBoss, Home of Professional Open Source.
* Copyright 2025 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.undertow.server.handlers;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.AttachmentKey;
import io.undertow.util.PathTemplateParser;
import io.undertow.util.PathTemplateRouter;
import io.undertow.util.PathTemplateRouteResult;
import io.undertow.util.PathTemplateRouterFactory;
import java.util.function.Supplier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* A drop-in substitute for the old PathTemplateHandler class. Ideally, one should use {@link PathTemplateRouterHandler} by
* instantiating it with a {@link PathTemplateRouterFactory}. This class implements all of the methods from the original
* PathTemplateHandler to provide backwards compatibility.
*
* @author Dirk Roets. This class was originally written by Stuart Douglas. After the introduction of
* {@link PathTemplateRouterFactory}, it was rewritten against the original interface and tests.
*/
public class PathTemplateHandler implements HttpHandler {
/**
* @see io.undertow.util.PathTemplateMatch#ATTACHMENT_KEY
*/
@Deprecated
public static final AttachmentKey<PathTemplateHandler.PathTemplateMatch> PATH_TEMPLATE_MATCH = AttachmentKey.
create(PathTemplateHandler.PathTemplateMatch.class);
private final boolean rewriteQueryParameters;
private final PathTemplateRouterFactory.SimpleBuilder<HttpHandler> builder;
private final Object lock = new Object();
private volatile PathTemplateRouter<HttpHandler> router;
/**
* Default constructor. Uses {@link ResponseCodeHandler#HANDLE_404} as the next (default) handler and sets
* 'rewriteQueryParameters' to 'true'.
*/
public PathTemplateHandler() {
this(true);
}
/**
* Uses {@link ResponseCodeHandler#HANDLE_404} as the next (default) handler.
*
* @param rewriteQueryParameters Path parameters that are returned by the underlying router will be added as query
* parameters to the exchange if this flag is 'true'.
*/
public PathTemplateHandler(final boolean rewriteQueryParameters) {
this(ResponseCodeHandler.HANDLE_404, rewriteQueryParameters);
}
/**
* Sets 'rewriteQueryParameters' to 'true'.
*
* @param next The next (default) handler to use when requests do not match any of the specified templates.
*/
public PathTemplateHandler(final HttpHandler next) {
this(next, true);
}
/**
* @param next The next (default) handler to use when requests do not match any of the specified templates.
* @param rewriteQueryParameters Path parameters that are returned by the underlying router will be added as query
* parameters to the exchange if this flag is 'true'.
*/
public PathTemplateHandler(final HttpHandler next, final boolean rewriteQueryParameters) {
Objects.requireNonNull(next);
this.rewriteQueryParameters = rewriteQueryParameters;
builder = PathTemplateRouterFactory.SimpleBuilder.newBuilder(next);
router = builder.build();
}
/**
* Adds a template and handler to the underlying router.
*
* @param uriTemplate The URI path template.
* @param handler The handler to use for requests that match the specified template.
*
* @return Reference to this handler.
*/
public PathTemplateHandler add(final String uriTemplate, final HttpHandler handler) {
Objects.requireNonNull(uriTemplate);
Objects.requireNonNull(handler);
// PathTemplateRouter builders are not thread-safe, so we need to synchronize.
synchronized (lock) {
builder.addTemplate(uriTemplate, handler);
router = builder.build();
}
return this;
}
/**
* Removes a template from the underlying router.
*
* @param uriTemplate The URI path template.
*
* @return Reference to this handler.
*/
public PathTemplateHandler remove(final String uriTemplate) {
Objects.requireNonNull(uriTemplate);
// PathTemplateRouter builders are not thread-safe, so we need to synchronize.
synchronized (lock) {
builder.removeTemplate(uriTemplate);
router = builder.build();
}
return this;
}
@Override
public String toString() {
final List<PathTemplateParser.PathTemplatePatternEqualsAdapter<PathTemplateParser.PathTemplate<Supplier<HttpHandler>>>> templates
= new ArrayList<>(builder.getBuilder().getTemplates().keySet());
final StringBuilder sb = new StringBuilder();
sb.append("path-template( ");
if (templates.size() == 1) {
sb.append(templates.get(0).getPattern().getPathTemplate()).append(" )");
} else {
sb.append('{').append(
// Creates a ", " separated string of all patterns in this handler.
templates.stream().map(s -> s.getPattern().getPathTemplate()).collect(Collectors.joining(", "))
).append("} )");
}
return sb.toString();
}
@Override
public void handleRequest(final HttpServerExchange exchange) throws Exception {
final PathTemplateRouteResult<HttpHandler> routeResult = router.route(exchange.getRelativePath());
if (routeResult.getPathTemplate().isEmpty()) {
// This is the default handler, therefore it doesn't contain path parameters.
routeResult.getTarget().handleRequest(exchange);
return;
}
exchange.putAttachment(PATH_TEMPLATE_MATCH, new PathTemplateMatch(routeResult));
exchange.putAttachment(io.undertow.util.PathTemplateMatch.ATTACHMENT_KEY, routeResult);
if (rewriteQueryParameters) {
for (Map.Entry<String, String> entry : routeResult.getParameters().entrySet()) {
exchange.addQueryParam(entry.getKey(), entry.getValue());
}
}
routeResult.getTarget().handleRequest(exchange);
}
/**
* @see io.undertow.util.PathTemplateMatch
*/
@Deprecated
public static final class PathTemplateMatch {
private final io.undertow.util.PathTemplateMatch pathTemplateMatch;
PathTemplateMatch(
final io.undertow.util.PathTemplateMatch pathTemplateMatch
) {
this.pathTemplateMatch = Objects.requireNonNull(pathTemplateMatch);
}
public PathTemplateMatch(final String matchedTemplate, final Map<String, String> parameters) {
this(
new io.undertow.util.PathTemplateMatch(matchedTemplate, parameters)
);
}
public String getMatchedTemplate() {
return pathTemplateMatch.getMatchedTemplate();
}
public Map<String, String> getParameters() {
return pathTemplateMatch.getParameters();
}
}
}