Skip to content

Commit

Permalink
feat: Get menu views api (#19355)
Browse files Browse the repository at this point in the history
Create MenuRegistry for getting
automatic menu views for population.

Fixes #19321
  • Loading branch information
caalador authored May 15, 2024
1 parent 8b803ad commit 2f9bfc6
Show file tree
Hide file tree
Showing 11 changed files with 862 additions and 98 deletions.
26 changes: 1 addition & 25 deletions flow-server/src/main/java/com/vaadin/flow/router/MenuData.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,31 +24,7 @@
* <p>
* Only for read as data is immutable.
*/
public class MenuData implements Serializable {

private final String title;
private final Double order;
private final boolean exclude;
private final String icon;

/**
* Creates a new instance of the menu data.
*
* @param title
* the title of the menu item
* @param order
* the order of the menu item
* @param exclude
* whether the menu item should be excluded
* @param icon
* the icon of the menu item
*/
public MenuData(String title, Double order, boolean exclude, String icon) {
this.title = title;
this.order = order;
this.exclude = exclude;
this.icon = icon;
}
public record MenuData(String title, Double order, boolean exclude, String icon) implements Serializable {

/**
* Gets the title of the menu item.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,24 @@
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.internal.CurrentInstance;
import com.vaadin.flow.router.internal.AbstractRouteRegistry;
import com.vaadin.flow.router.internal.BeforeEnterHandler;
import com.vaadin.flow.router.internal.HasUrlParameterFormat;
import com.vaadin.flow.router.internal.PathUtil;
import com.vaadin.flow.router.internal.RouteUtil;
import com.vaadin.flow.server.Command;
import com.vaadin.flow.server.InvalidRouteConfigurationException;
import com.vaadin.flow.server.RouteRegistry;
import com.vaadin.flow.server.SessionRouteRegistry;
import com.vaadin.flow.server.VaadinRequest;
import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.VaadinServlet;
import com.vaadin.flow.server.VaadinSession;
Expand Down Expand Up @@ -531,4 +535,41 @@ private final boolean isAnnotatedParameter(
return false;
}

/**
* Get the {@link RouteData} for all accessible registered navigation
* targets with a menu information. Access checking depends on the active
* {@link VaadinService} and {@link VaadinRequest}.
* <p>
* Automatically adds access controls from UI if available.
*
* @return list of accessible menu routes available for handled registry
*/
public List<RouteData> getRegisteredAccessibleMenuRoutes() {
UI ui = UI.getCurrent();
if (ui != null) {
List<BeforeEnterListener> accessControls = ui.getInternals()
.getListeners(BeforeEnterHandler.class).stream()
.filter(BeforeEnterListener.class::isInstance)
.map(BeforeEnterListener.class::cast).toList();
return getRegisteredAccessibleMenuRoutes(accessControls);
}

return getRegisteredAccessibleMenuRoutes(Collections.emptyList());
}

/**
* Get the {@link RouteData} for all accessible registered navigation
* targets with a menu information. Access checking depends on the active
* {@link VaadinService} and {@link VaadinRequest} and the given collection
* of access controls.
*
* @param accessControls
* the access controls to use for checking access
* @return list of accessible menu routes available for handled registry
*/
public List<RouteData> getRegisteredAccessibleMenuRoutes(
Collection<BeforeEnterListener> accessControls) {
return getHandledRegistry().getRegisteredAccessibleMenuRoutes(
VaadinRequest.getCurrent(), accessControls);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import com.vaadin.flow.server.auth.NavigationAccessControl;
import com.vaadin.flow.server.auth.NavigationContext;
import com.vaadin.flow.server.auth.ViewAccessChecker;
import com.vaadin.flow.server.menu.MenuRegistry;
import com.vaadin.flow.shared.Registration;

import static java.util.stream.Collectors.toList;
Expand Down Expand Up @@ -308,7 +309,10 @@ private void populateRegisteredRoutes(ConfiguredRoutes configuration,

MenuData menuData = AnnotationReader
.getAnnotationFor(target, Menu.class)
.map(menu -> new MenuData(menu.title(),
.map(menu -> new MenuData(
(menu.title() == null || menu.title().isBlank())
? MenuRegistry.getTitle(target)
: menu.title(),
(Objects.equals(menu.order(), Double.MIN_VALUE)) ? null
: menu.order(),
false, menu.icon()))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2000-2024 Vaadin Ltd.
*
* 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 com.vaadin.flow.router.internal;

import java.io.Serializable;
import java.util.Optional;

/**
* Define a route url parameter details.
* <p>
* For internal use only. May be renamed or removed in a future release.
*/
public class ParameterInfo implements Serializable {

private final String name;

private final String template;

private final boolean optional;

private final boolean varargs;

private final String regex;

public ParameterInfo(String template) {
this.template = template;

if (!RouteFormat.isParameter(template)) {
throw new IllegalArgumentException(
"The given string is not a parameter template.");
}

optional = RouteFormat.isOptionalParameter(template);
if (optional) {
template = template.replaceFirst("\\?", "");
}
varargs = RouteFormat.isVarargsParameter(template);
if (varargs) {
template = template.replaceFirst("\\*", "");
}

// Remove :
template = template.substring(1);

// Extract the template defining the value of the parameter.
final int regexStartIndex = template.indexOf('(');
if (regexStartIndex != -1) {

name = template.substring(0, regexStartIndex);

regex = template.substring(regexStartIndex + 1,
template.length() - 1);
} else {
name = template;
regex = null;
}
}

public String getName() {
return name;
}

public String getTemplate() {
return template;
}

public boolean isOptional() {
return optional;
}

public boolean isVarargs() {
return varargs;
}

public Optional<String> getRegex() {
return Optional.ofNullable(regex);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,74 +173,4 @@ static Optional<String> formatSegmentRegex(RouteSegment segment,
}
}

/**
* Define a route url parameter details.
*/
static class ParameterInfo implements Serializable {

private final String name;

private final String template;

private final boolean optional;

private final boolean varargs;

private final String regex;

ParameterInfo(String template) {
this.template = template;

if (!isParameter(template)) {
throw new IllegalArgumentException(
"The given string is not a parameter template.");
}

optional = isOptionalParameter(template);
if (optional) {
template = template.replaceFirst("\\?", "");
}
varargs = isVarargsParameter(template);
if (varargs) {
template = template.replaceFirst("\\*", "");
}

// Remove :
template = template.substring(1);

// Extract the template defining the value of the parameter.
final int regexStartIndex = template.indexOf('(');
if (regexStartIndex != -1) {

name = template.substring(0, regexStartIndex);

regex = template.substring(regexStartIndex + 1,
template.length() - 1);
} else {
name = template;
regex = null;
}
}

public String getName() {
return name;
}

public String getTemplate() {
return template;
}

public boolean isOptional() {
return optional;
}

public boolean isVarargs() {
return varargs;
}

public Optional<String> getRegex() {
return Optional.ofNullable(regex);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ final class RouteSegment implements Serializable {
/**
* Parameter details.
*/
private RouteFormat.ParameterInfo info;
private ParameterInfo info;

/**
* Parameter matching regex.
Expand Down Expand Up @@ -118,7 +118,7 @@ private RouteSegment(String segmentTemplate, boolean isRoot) {
this.isRoot = isRoot;

if (RouteFormat.isParameter(segmentTemplate)) {
info = new RouteFormat.ParameterInfo(segmentTemplate);
info = new ParameterInfo(segmentTemplate);

getRegex().ifPresent(s -> pattern = Pattern.compile(s));

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright 2000-2024 Vaadin Ltd.
*
* 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 com.vaadin.flow.server.menu;

import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonProperty;

import com.vaadin.flow.router.MenuData;

/**
* Represents a view configuration for use with a menu.
*
* @param title
* @param rolesAllowed
* @param loginRequired
* @param route
* @param lazy
* @param register
* @param menu
* @param children
* @param routeParameters
*/
public record AvailableViewInfo(String title, String[] rolesAllowed,
boolean loginRequired, String route, boolean lazy,
boolean register, MenuData menu,
List<AvailableViewInfo> children, @JsonProperty(
"params") Map<String, RouteParamType> routeParameters) implements Serializable {

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
} else if (o == null || getClass() != o.getClass()) {
return false;
}
final AvailableViewInfo that = (AvailableViewInfo) o;
return Objects.equals(title, that.title)
&& Arrays.equals(rolesAllowed, that.rolesAllowed)
&& Objects.equals(loginRequired, that.loginRequired)
&& Objects.equals(route, that.route)
&& Objects.equals(lazy, that.lazy)
&& Objects.equals(register, that.register)
&& Objects.equals(menu, that.menu)
&& Objects.equals(routeParameters, that.routeParameters);
}

@Override
public int hashCode() {
int result = Objects.hash(title, loginRequired, route, lazy, register, menu, routeParameters);
result = 31 * result + Arrays.hashCode(rolesAllowed);
return result;
}

@Override
public String toString() {
return "AvailableViewInfo{" + "title='" + title
+ '\'' + ", rolesAllowed=" + Arrays.toString(rolesAllowed)
+ ", loginRequired=" + loginRequired
+ ", route='" + route + '\''
+ ", lazy=" + lazy
+ ", register=" + register
+ ", menu=" + menu
+ ", routeParameters=" + routeParameters + '}';
}

}
Loading

0 comments on commit 2f9bfc6

Please sign in to comment.