org.teamapps.ux.session.navigation.Router Maven / Gradle / Ivy
/*-
* ========================LICENSE_START=================================
* TeamApps
* ---
* Copyright (C) 2014 - 2024 TeamApps.org
* ---
* 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.
* =========================LICENSE_END==================================
*/
package org.teamapps.ux.session.navigation;
import org.glassfish.jersey.uri.UriTemplate;
import org.teamapps.ux.session.SessionContext;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static org.teamapps.ux.session.navigation.NavigationHistoryOperation.PUSH;
import static org.teamapps.ux.session.navigation.NavigationHistoryOperation.REPLACE;
import static org.teamapps.ux.session.navigation.RoutingUtil.*;
public class Router {
static final String PATH_REMAINDER_VARNAME = "_remainder";
static final String PATH_REMAINDER_SUFFIX = "{" + PATH_REMAINDER_VARNAME + ":(/.*)?}";
private final String pathPrefix;
private final UriTemplate pathPrefixTemplate;
private final UriTemplate pathPrefixExactTemplate;
private final List handlers = new CopyOnWriteArrayList<>(); // handler execution might add more handlers => prevent ConcurrentModificationException
private String path;
private NavigationHistoryOperation pathChangeOperation;
private final Map> queryParams = new HashMap<>();
private Supplier pathSupplier;
private NavigationHistoryOperation pathSupplierChangeOperation;
private final Map>> queryParameterSuppliers = new HashMap<>();
private final List>>> queryParametersSuppliers = new ArrayList<>();
private Supplier routeSupplier;
private NavigationHistoryOperation routeSupplierChangeOperation;
private final List routeHandlingChangeListeners = new ArrayList<>();
public Router(String pathPrefix) {
this.pathPrefix = normalizePath(pathPrefix);
this.pathPrefixTemplate = createUriTemplate(this.pathPrefix, false);
this.pathPrefixExactTemplate = createUriTemplate(this.pathPrefix, true);
}
private UriTemplate createUriTemplate(String path, boolean exact) {
path = normalizePath(path);
String templateString;
if (exact) {
templateString = path;
} else {
templateString = isEmptyPath(path) ? PATH_REMAINDER_SUFFIX : path + PATH_REMAINDER_SUFFIX;
}
return new UriTemplate(templateString);
}
public RelativeRouteInfo calculateRelativeRouteInfo() {
Route route = Route.create();
NavigationHistoryOperation pathChangeOperation = REPLACE;
Set queryParameterNamesTriggeringPush = new HashSet<>();
if (routeSupplier != null) {
route = routeSupplier.get();
pathChangeOperation = routeSupplierChangeOperation;
if (routeSupplierChangeOperation == PUSH) {
queryParameterNamesTriggeringPush.addAll(route.getQueryParams().keySet());
}
}
for (WithNavigationHistoryChangeOperation>> supplierAndOperation : queryParametersSuppliers) {
Map queryParameters = supplierAndOperation.getValue().get();
route = route.withQueryParams(queryParameters);
if (supplierAndOperation.getNavigationHistoryOperation() == PUSH) {
queryParameterNamesTriggeringPush.addAll(queryParameters.keySet());
} else {
queryParameterNamesTriggeringPush.removeAll(queryParameters.keySet());
}
}
for (var entry : queryParameterSuppliers.entrySet()) {
String paramName = entry.getKey();
String paramValue = entry.getValue().getValue().get();
NavigationHistoryOperation changeOperation = entry.getValue().getNavigationHistoryOperation();
route = route.withQueryParam(paramName, paramValue);
if (changeOperation == PUSH) {
queryParameterNamesTriggeringPush.add(paramName);
} else {
queryParameterNamesTriggeringPush.remove(paramName);
}
}
if (pathSupplier != null) {
String path = pathSupplier.get();
route = route.withPath(path);
pathChangeOperation = pathSupplierChangeOperation;
}
for (var entry : queryParams.entrySet()) {
String paramName = entry.getKey();
String paramValue = entry.getValue().getValue();
NavigationHistoryOperation changeOperation = entry.getValue().getNavigationHistoryOperation();
route = route.withQueryParam(paramName, paramValue);
if (changeOperation == PUSH) {
queryParameterNamesTriggeringPush.add(paramName);
} else {
queryParameterNamesTriggeringPush.remove(paramName);
}
}
if (path != null) {
route = route.withPath(path);
pathChangeOperation = this.pathChangeOperation;
}
return new RelativeRouteInfo(route, pathChangeOperation == PUSH, queryParameterNamesTriggeringPush);
}
public Registration registerRouteHandler(String pathTemplate, RouteHandler handler) {
return registerRouteHandler(pathTemplate, false, handler);
}
public Registration registerRouteHandler(String pathTemplate, boolean exact, RouteHandler handler) {
var uriTemplate = createUriTemplate(pathTemplate, exact);
UriTemplateAndHandler templateAndRouter = new UriTemplateAndHandler(uriTemplate, handler);
handlers.add(templateAndRouter);
handlers.sort(Comparator.comparing(h -> h.getUriTemplate().getTemplate().length())); // shorter ones should go first (let's say)
fireRouteHandlingChange();
return () -> handlers.remove(templateAndRouter);
}
public Registration registerRouteHandlers(Object annotatedClassInstance) {
List registrations = new AnnotationBasedRouteHandlerFactory(SessionContext.current().getRoutingParamConverterProvider())
.createRouteHandlers(annotatedClassInstance).stream()
.map(handler -> registerRouteHandler(handler.getPathTemplate(), handler.isExact(), handler))
.collect(Collectors.toList());
fireRouteHandlingChange();
return () -> registrations.forEach(Registration::dispose);
}
public Router getSubRouter(String relativePath) {
return SessionContext.current().getRouter(concatenatePaths(pathPrefix, relativePath));
}
public void route(Route route) {
for (UriTemplateAndHandler uriTemplateAndHandler : handlers) {
HashMap pathParams = new HashMap<>();
boolean matches = uriTemplateAndHandler.getUriTemplate().match(route.getPath(), pathParams);
if (matches) {
uriTemplateAndHandler.getHandler().handle(route.getPath(), pathParams, route.getQueryParams());
}
}
}
public void setPath(String path) {
setPath(path, PUSH);
}
public void setPath(String path, NavigationHistoryOperation changeOperation) {
this.path = path;
pathChangeOperation = changeOperation;
}
public void setQueryParameter(String parameterName, String value) {
setQueryParameter(parameterName, value, PUSH);
}
public void setQueryParameter(String parameterName, String value, NavigationHistoryOperation changeOperation) {
this.queryParams.put(parameterName, new WithNavigationHistoryChangeOperation<>(value, changeOperation));
}
public Registration setPathSupplier(Supplier pathSupplier) {
return setPathSupplier(pathSupplier, PUSH);
}
public Registration setPathSupplier(Supplier pathSupplier, NavigationHistoryOperation changeOperation) {
this.pathSupplier = pathSupplier;
pathSupplierChangeOperation = changeOperation;
return () -> {
if (this.pathSupplier == pathSupplier) {
this.pathSupplier = null;
}
};
}
public Registration setQueryParameterSupplier(String parameterName, Supplier supplier) {
return setQueryParameterSupplier(parameterName, supplier, PUSH);
}
public Registration setQueryParameterSupplier(String parameterName, Supplier supplier, NavigationHistoryOperation changeOperation) {
this.queryParameterSuppliers.put(parameterName, new WithNavigationHistoryChangeOperation<>(supplier, changeOperation));
return () -> {
if (this.queryParameterSuppliers.get(parameterName) != null && this.queryParameterSuppliers.get(parameterName).getValue() == supplier) {
queryParameterSuppliers.remove(parameterName);
}
};
}
public Registration addQueryParametersSupplier(Supplier
© 2015 - 2024 Weber Informatics LLC | Privacy Policy