Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,10 @@ public class ApplicationMetadataController {
private final ApplicationMetadataLoader metadataLoader;
private final EntityMetadata unknowEntity;
/**
* Cached application metadata.
*/
private ApplicationMetadata cache;
/**
* Cached entities metadata.
* Cached entities metadata. Application metadata and global actions are deliberately not cached because
* their texts depend on the request locale (e.g. per-tenant), unlike the much larger entities metadata.
*/
private ApplicationMetadataEntities entities;
/**
* Cached global actions metadata.
*/
private ApplicationMetadataActions globalActions;
/**
* Cache for individual entity metadata.
*/
Expand Down Expand Up @@ -114,10 +107,7 @@ private void initMetadata() {
*/
@GetMapping(value = "", produces = "application/json")
public ApplicationMetadata getMetadata() {
if (cache == null) {
cache = metadataLoader.load();
}
return cache;
return metadataLoader.load();
}

/**
Expand All @@ -137,10 +127,7 @@ public NavigationTree getNavigation() {
*/
@GetMapping(value = "/actions", produces = "application/json")
public ApplicationMetadataActions getGlobalActions() {
if (globalActions == null) {
globalActions = metadataLoader.loadGlobalActions();
}
return globalActions;
return metadataLoader.loadGlobalActions();
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package tools.dynamia.navigation;

import tools.dynamia.commons.LocalizedMessagesProvider;
import tools.dynamia.commons.logger.LoggingService;
import tools.dynamia.integration.Containers;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;

/**
* Resolves navigation labels through the registered {@link LocalizedMessagesProvider}s, independent of any UI
* technology. The classifier is always {@code null}: it is the provider's job to derive any grouping it needs
* from the key (the element's virtual path).
*/
final class NavigationLabels {

private static final LoggingService LOGGER = LoggingService.get(NavigationLabels.class);

private NavigationLabels() {
}

/**
* Returns the registered providers sorted by priority (lower number first), or an empty list when no
* container is available.
*/
static List<LocalizedMessagesProvider> providers() {
try {
return Containers.get().findObjects(LocalizedMessagesProvider.class).stream()
.sorted(Comparator.comparingInt(LocalizedMessagesProvider::getPriority))
.toList();
} catch (RuntimeException e) {
return Collections.emptyList();
}
}

/**
* Returns the first non-null provider message for the key, or {@code defaultValue} when none provides one.
* A failing provider is skipped so it can never break the navigation tree.
*/
static String resolve(List<LocalizedMessagesProvider> providers, String key, Locale locale, String defaultValue) {
for (var provider : providers) {
try {
var message = provider.getMessage(key, null, locale, defaultValue);
if (message != null) {
return message;
}
} catch (RuntimeException e) {
LOGGER.warn("Localized messages provider " + provider.getClass().getName() + " failed for key " + key + ": " + e.getMessage());
}
}
return defaultValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import tools.dynamia.commons.Messages;

import java.io.Serializable;
import java.util.ArrayList;
Expand Down Expand Up @@ -50,9 +51,13 @@ public NavigationNode(String id, String name, String internalPath) {
public NavigationNode(NavigationElement element) {
this.element = element;
this.id = element.getId();
this.name = element.getLocalizedName();
var locale = Messages.getDefaultLocale();
var providers = NavigationLabels.providers();
this.name = NavigationLabels.resolve(providers, element.getVirtualPath(), locale, element.getLocalizedName(locale));
this.longName = element.getLongNameSupplier() != null ? (String) element.getLongNameSupplier().get() : element.getLongName();
this.description = element.getLocalizedDescription();
var defaultDescription = element.getLocalizedDescription(locale);
this.description = defaultDescription == null || defaultDescription.isBlank() ? defaultDescription
: NavigationLabels.resolve(providers, element.getVirtualPath() + ".description", locale, defaultDescription);
this.icon = element.getIcon();
this.internalPath = element.getVirtualPath();
this.path = element.getPrettyVirtualPath();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package tools.dynamia.navigation;

import org.junit.jupiter.api.Test;
import tools.dynamia.commons.LocalizedMessagesProvider;

import java.util.List;
import java.util.Locale;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

class NavigationLabelsTest {

private static LocalizedMessagesProvider provider(String value) {
return (key, classifier, locale, defaultValue) -> {
assertNull(classifier);
return value;
};
}

@Test
void firstNonNullProviderWins() {
var providers = List.of(provider(null), provider("Ventas"), provider("Sales"));
assertEquals("Ventas", NavigationLabels.resolve(providers, "store/sales", Locale.of("es"), "Sales"));
}

@Test
void fallsBackToDefaultWithoutProviders() {
assertEquals("Sales", NavigationLabels.resolve(List.of(), "store/sales", Locale.ENGLISH, "Sales"));
}

@Test
void failingProviderIsSkipped() {
LocalizedMessagesProvider failing = (key, classifier, locale, defaultValue) -> {
throw new IllegalStateException("boom");
};
assertEquals("Ventas", NavigationLabels.resolve(List.of(failing, provider("Ventas")), "store/sales", Locale.ENGLISH, "Sales"));
}

@Test
void nodeUsesLocalizedElementNameWhenNoProviders() {
var page = new Page("sales", "Sales", "store/sales");
assertEquals("Sales", new NavigationNode(page).getName());
}
}
Loading