
io.rxmicro.rest.server.internal.Router Maven / Gradle / Ivy
/*
* Copyright (c) 2020. https://rxmicro.io
*
* 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.rxmicro.rest.server.internal;
import io.rxmicro.config.ConfigException;
import io.rxmicro.logger.Logger;
import io.rxmicro.logger.LoggerFactory;
import io.rxmicro.rest.model.PathVariableMapping;
import io.rxmicro.rest.server.RestServerConfig;
import io.rxmicro.rest.server.detail.component.AbstractRestController;
import io.rxmicro.rest.server.detail.model.HttpRequest;
import io.rxmicro.rest.server.detail.model.HttpResponse;
import io.rxmicro.rest.server.detail.model.PathMatcherResult;
import io.rxmicro.rest.server.detail.model.Registration;
import io.rxmicro.rest.server.detail.model.mapping.ExactUrlRequestMappingRule;
import io.rxmicro.rest.server.detail.model.mapping.RequestMappingRule;
import io.rxmicro.rest.server.detail.model.mapping.UrlTemplateRequestMappingRule;
import io.rxmicro.rest.server.internal.component.ComponentResolver;
import io.rxmicro.rest.server.internal.component.RequestMappingKeyBuilder;
import io.rxmicro.rest.server.local.component.DynamicRestControllerRegistrar;
import io.rxmicro.rest.server.local.component.RequestHandler;
import io.rxmicro.rest.server.local.model.RestControllerMethod;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import static io.rxmicro.rest.model.HttpMethod.OPTIONS;
import static io.rxmicro.rest.model.PathVariableMapping.NO_PATH_VARIABLES;
import static java.util.Collections.unmodifiableSet;
import static java.util.Map.entry;
import static java.util.concurrent.CompletableFuture.completedStage;
/**
* @author nedis
* @since 0.1
*/
public final class Router implements DynamicRestControllerRegistrar, RequestHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(Router.class);
private final Map exactUrlRestControllerMap =
new ConcurrentHashMap<>();
private final List> urlTemplateRestControllers =
new CopyOnWriteArrayList<>();
private final Set registeredRestControllers = new HashSet<>();
private final Set registeredRequestMappingRules = new HashSet<>();
private final ComponentResolver componentResolver;
private final RestServerConfig restServerConfig;
private final CompletionStage handlerNotFoundStage;
private final CompletionStage corsNotAllowedStage;
private final RequestMappingKeyBuilder requestMappingKeyBuilder;
public Router(final ComponentResolver componentResolver) {
this.componentResolver = componentResolver;
this.restServerConfig = componentResolver.getRestServerConfig();
this.requestMappingKeyBuilder = componentResolver.getRequestMappingKeyBuilder();
this.handlerNotFoundStage = completedStage(
componentResolver.getHttpErrorResponseBodyBuilder().build(
componentResolver.getHttpResponseBuilder(),
componentResolver.getRestServerConfig().getHandlerNotFoundErrorStatusCode(),
componentResolver.getRestServerConfig().getHandlerNotFoundErrorMessage()
)
);
this.corsNotAllowedStage = completedStage(
componentResolver.getHttpErrorResponseBodyBuilder().build(
componentResolver.getHttpResponseBuilder(),
componentResolver.getRestServerConfig().getCorsNotAllowedErrorStatusCode(),
componentResolver.getRestServerConfig().getCorsNotAllowedErrorMessage()
)
);
}
@Override
public void register(final AbstractRestController restController,
final Registration... registrations) {
if (registrations.length == 0) {
throw new ConfigException("'?' does not have request mapping", restController.getRestControllerClass());
}
if (registeredRestControllers.contains(restController)) {
throw new ConfigException("'?' already registered", restController.getRestControllerClass());
}
injectDependencies(restController);
registeredRestControllers.add(restController);
addRegistrations(restController, registrations);
}
private void injectDependencies(final BaseRestController restController) {
restController.httpResponseBuilder = componentResolver.getHttpResponseBuilder();
restController.restServerConfig = componentResolver.getRestServerConfig();
restController.httpErrorResponseBodyBuilder = componentResolver.getHttpErrorResponseBodyBuilder();
restController.postConstruct();
}
private void addRegistrations(final AbstractRestController restController,
final Registration... registrations) {
Arrays.stream(registrations).forEach(registration -> {
final RestControllerMethod method = new RestControllerMethod(
registration.getParentUrl(),
restController,
registration.getMethodName(),
registration.getParamTypes(),
registration.getMethod(),
registration.isCorsRequestPossible()
);
registration.getRequestMappingRules().forEach(requestMapping -> {
if (!registeredRequestMappingRules.add(requestMapping)) {
throw new ConfigException("Request mapping '?' not unique", requestMapping);
}
if (requestMapping.isExactUrlRequestMappingRule()) {
final String requestMappingKey = requestMappingKeyBuilder.build((ExactUrlRequestMappingRule) requestMapping);
exactUrlRestControllerMap.put(requestMappingKey, method);
} else {
urlTemplateRestControllers.add(entry((UrlTemplateRequestMappingRule) requestMapping, method));
}
LOGGER.info("Mapped ? onto ?", () -> requestMapping, () -> getMethodName(method));
});
});
}
private String getMethodName(final RestControllerMethod method) {
if (restServerConfig.isUseFullClassNamesForRouterMappingLogMessages()) {
return method.getFullName();
} else {
return method.getShortName();
}
}
@Override
public void clear() {
exactUrlRestControllerMap.clear();
registeredRestControllers.clear();
registeredRequestMappingRules.clear();
}
@Override
public Set getRegisteredRestControllers() {
return unmodifiableSet(registeredRestControllers);
}
@Override
public CompletionStage handle(final HttpRequest request) {
final String requestMappingKey = requestMappingKeyBuilder.build(request);
final RestControllerMethod restControllerMethod = exactUrlRestControllerMap.get(requestMappingKey);
if (restControllerMethod != null) {
return restControllerMethod.call(NO_PATH_VARIABLES, request);
}
for (final var entry : urlTemplateRestControllers) {
final PathMatcherResult match = entry.getKey().match(request);
if (match.matches()) {
return entry.getValue().call(newPathVariableMapping(entry, match), request);
}
}
if (OPTIONS.name().equals(request.getMethod())) {
LOGGER.error(request, "CORS not allowed: Handler not found: ?", requestMappingKey);
return corsNotAllowedStage;
} else {
LOGGER.error(request, "Handler not found: ?", requestMappingKey);
return handlerNotFoundStage;
}
}
private PathVariableMapping newPathVariableMapping(
final Map.Entry entry,
final PathMatcherResult match) {
return new PathVariableMapping(
entry.getKey().getVariables(),
match.getExtractedVariableValues()
);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy