Merge branch 'eclipse-main' into translation

This commit is contained in:
Axel Uhl committed 2026-09-18 20:49:53 +02:00
commit cd5def0111
28 files changed
+224 -59

No files matched your search

@@ -13,6 +13,7 @@ public enum BoatClassMasterdata {
_18Footer ("18Footer", true, 8.90, 2.00, BoatHullType.MONOHULL, true, "18.Footer", "18ft", "18ft Skiff", "18. Footer"),
_2_4M ("2.4 Meter", true, 4.11, 0.81, BoatHullType.MONOHULL, false, "2.4M", "2.4mR", "2.4 Metre", "2.4-metre", "24MR"),
_12M ("12 Meter", true, 21.50, 3.60, BoatHullType.MONOHULL, true, "12M", "12mR", "12SQM", "12-metre", "12 metre"),
_15M2_SNS ("15m2 SNS", true, 7.00, 1.95, BoatHullType.MONOHULL, true, "15m2", "15er", "15 m2 SNS", "15m² SNS", "SNS 15"),
_29ER ("29er", true, 4.45, 1.70, BoatHullType.MONOHULL, true, "29-ER"),
_49ER ("49er", true, 4.88, 1.93, BoatHullType.MONOHULL, true, "49-ER"),
_49ERFX ("49erFX", true, 4.88, 1.93, BoatHullType.MONOHULL, true, "49FX"),
@@ -99,6 +100,7 @@ public enum BoatClassMasterdata {
LASER_INT ("Laser Int.", true, 4.19, 1.39, BoatHullType.MONOHULL, false, "Laser", "LSR", "Laser Standard", "ILCA 7", "ILCA7"),
LASER_SB3 ("Laser SB3", true, 6.15, 2.15, BoatHullType.MONOHULL, false, "LSB3", "SB20"),
LAGO_26 ("Lago 26", true, 7.95, 2.50, BoatHullType.MONOHULL, true, "Lago26"),
LACUSTRE ("Lacustre", true, 9.50, 1.81, BoatHullType.MONOHULL, true, "Lacustre Int."),
LONGTZE ("Longtze", true, 6.85, 2.57, BoatHullType.MONOHULL, true, "Swiss Longtze Class"),
M32 ("M32", false, 9.70, 5.50, BoatHullType.CATAMARAN, true, "M/32", "M32 Catamaran", "M/32 Catamaran"),
MELGES_20 ("Melges 20", true, 6.10, 2.13, BoatHullType.MONOHULL, true, "Melges-20", "M20"),
@@ -111,6 +113,7 @@ public enum BoatClassMasterdata {
NACRA_17_FOIL ("Nacra 17 Foiling", true, 5.25, 2.59, BoatHullType.CATAMARAN, true, "N17F", "Nacra-17-Foiling"),
O_JOLLE ("O-Jolle", true, 5.00, 1.66, BoatHullType.MONOHULL, false, "O Jolle", "OJolle", "Olympiajolle"),
OK ("OK Dinghy", true, 5.25, 2.59, BoatHullType.MONOHULL, false, "OK-Dinghy", "OK-Jolle", "OK"),
ONYX ("Onyx", true, 8.50, 2.20, BoatHullType.MONOHULL, true, "Onyx Yacht"),
OPEN_BIC("O'pen BIC", true, 2.75, 1.14, BoatHullType.MONOHULL, false, "OpenBIC"),
OPTIMIST ("Optimist", true, 2.34, 1.07, BoatHullType.MONOHULL, false, "Opti", "Optimist Dinghy"),
PIRATE ("Pirate", true, 5.00, 1.61, BoatHullType.MONOHULL, false, "Pirat", "Piraten"),
@@ -27,6 +27,7 @@ public class BoatClassImageResolver {
boatClassIconsMap.put(BoatClassMasterdata.PHRF.getDisplayName(), imageResources.PHRFIcon());
boatClassIconsMap.put(BoatClassMasterdata._12M.getDisplayName(), imageResources._12mRIcon());
boatClassIconsMap.put(BoatClassMasterdata._18Footer.getDisplayName(), imageResources._18FooterIcon());
boatClassIconsMap.put(BoatClassMasterdata._15M2_SNS.getDisplayName(), imageResources.SNS15m2Icon());
boatClassIconsMap.put(BoatClassMasterdata._2_4M.getDisplayName(), imageResources._2_4mIcon());
boatClassIconsMap.put(BoatClassMasterdata._29ER.getDisplayName(), imageResources._29erIcon());
boatClassIconsMap.put(BoatClassMasterdata._420.getDisplayName(), imageResources._420Icon());
@@ -114,6 +115,7 @@ public class BoatClassImageResolver {
boatClassIconsMap.put(BoatClassMasterdata.LASER_4_7.getDisplayName(), imageResources.Laser47Icon());
boatClassIconsMap.put(BoatClassMasterdata.LASER_RADIAL.getDisplayName(), imageResources.LaserRadialIcon());
boatClassIconsMap.put(BoatClassMasterdata.LAGO_26.getDisplayName(), imageResources.Lago26Icon());
boatClassIconsMap.put(BoatClassMasterdata.LACUSTRE.getDisplayName(), imageResources.LacustreIcon());
boatClassIconsMap.put(BoatClassMasterdata.LONGTZE.getDisplayName(), imageResources.LongtzeIcon());
boatClassIconsMap.put(BoatClassMasterdata.M32.getDisplayName(), imageResources.M32Icon());
boatClassIconsMap.put(BoatClassMasterdata.MELGES_20.getDisplayName(), imageResources.Melges20Icon());
@@ -126,6 +128,7 @@ public class BoatClassImageResolver {
boatClassIconsMap.put(BoatClassMasterdata.NACRA_17_FOIL.getDisplayName(), imageResources.Nacra17FoilIcon());
boatClassIconsMap.put(BoatClassMasterdata.O_JOLLE.getDisplayName(), imageResources.OJolleIcon());
boatClassIconsMap.put(BoatClassMasterdata.OK.getDisplayName(), imageResources.OKIcon());
boatClassIconsMap.put(BoatClassMasterdata.ONYX.getDisplayName(), imageResources.OnyxIcon());
boatClassIconsMap.put(BoatClassMasterdata.OPEN_BIC.getDisplayName(), imageResources.OpenBicIcon());
boatClassIconsMap.put(BoatClassMasterdata.OPTIMIST.getDisplayName(), imageResources.OptimistIcon());
boatClassIconsMap.put(BoatClassMasterdata.PIRATE.getDisplayName(), imageResources.PiratIcon());
@@ -191,6 +191,10 @@ public interface BoatClassImageResources extends ClientBundle {
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/FAREAST28R.png")
@ImageOptions(preventInlining = true)
ImageResource Fareast28RIcon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/15M2_SNS.png")
@ImageOptions(preventInlining = true)
ImageResource SNS15m2Icon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/FARR_30.png")
@ImageOptions(preventInlining = true)
@@ -316,6 +320,10 @@ public interface BoatClassImageResources extends ClientBundle {
@ImageOptions(preventInlining = true)
ImageResource Lago26Icon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/LACUSTRE.png")
@ImageOptions(preventInlining = true)
ImageResource LacustreIcon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/LONGTZE.png")
@ImageOptions(preventInlining = true)
ImageResource LongtzeIcon();
@@ -359,6 +367,10 @@ public interface BoatClassImageResources extends ClientBundle {
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/OK.png")
@ImageOptions(preventInlining = true)
ImageResource OKIcon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/ONYX.png")
@ImageOptions(preventInlining = true)
ImageResource OnyxIcon();
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/OPTIMIST.png")
@ImageOptions(preventInlining = true)
@@ -8,6 +8,7 @@
<div class="innerContent">
<h5 class="articleSubheadline">September 2026</h5>
<ul class="bulletList">
<li>Added boat classes Onyx, Lacustre and 15m&sup2; SNS.</li>
<li>Fix for recently introduced issue in the simulator when used for live smartphone-tracked
races. A time point from the mark passing detection turned out to be non-serializable.</li>
</ul>
@@ -17,6 +17,7 @@ import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.ui.client.SailingServiceWriteAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel;
import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel;
@@ -106,7 +107,7 @@ abstract class IPBlocklistTableWrapper
@Override
public void onClick(ClickEvent event) {
for (IpToTimedLockDTO e : getSelectionModel().getSelectedSet()) {
unlockIP(e.getIp(), new AsyncCallback<Void>() {
unlockIP(e.getIp(), new MarkedAsyncCallback<>(new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(errorMessageOnDataFailureString);
@@ -116,26 +117,27 @@ abstract class IPBlocklistTableWrapper
public void onSuccess(Void result) {
filterField.remove(e);
}
});
}));
}
}
});
}
private void loadDataAndPopulateTable() {
final AsyncCallback<ArrayList<IpToTimedLockDTO>> dataInitializationCallback = new AsyncCallback<ArrayList<IpToTimedLockDTO>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(errorMessageOnDataFailureString);
}
final AsyncCallback<ArrayList<IpToTimedLockDTO>> dataInitializationCallback = new MarkedAsyncCallback<>(
new AsyncCallback<ArrayList<IpToTimedLockDTO>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(errorMessageOnDataFailureString);
}
@Override
public void onSuccess(ArrayList<IpToTimedLockDTO> result) {
filterField.clear();
clear();
filterField.addAll(result);
}
};
@Override
public void onSuccess(ArrayList<IpToTimedLockDTO> result) {
filterField.clear();
clear();
filterField.addAll(result);
}
});
fetchData(dataInitializationCallback);
}
@@ -60,7 +60,9 @@ public class BoatClassVectorGraphicsResolver {
BoatClassMasterdata.BRASSFAHRT_I, BoatClassMasterdata.BRASSFAHRT_II, BoatClassMasterdata.BRASSFAHRT_III,
BoatClassMasterdata.BRASSFAHRT_IV, BoatClassMasterdata.BRASSFAHRT_V,
BoatClassMasterdata.VO60, BoatClassMasterdata.VO65, BoatClassMasterdata.IMOCA,
BoatClassMasterdata.HANSE_418, BoatClassMasterdata.SALONA_46, BoatClassMasterdata.SRS);
BoatClassMasterdata.HANSE_418, BoatClassMasterdata.SALONA_46, BoatClassMasterdata.SRS,
BoatClassMasterdata.ONYX, BoatClassMasterdata.LACUSTRE, BoatClassMasterdata._15M2_SNS,
BoatClassMasterdata.FAREAST28R);
BoatClassVectorGraphics dinghyWithSpinnaker = new DinghyWithSpinnakerVectorGraphics(BoatClassMasterdata._420,
BoatClassMasterdata._470, BoatClassMasterdata._5O5, BoatClassMasterdata.CADET, BoatClassMasterdata.FLYING_DUTCHMAN,
BoatClassMasterdata.FOLKBOAT, BoatClassMasterdata.DYAS, BoatClassMasterdata.DRAGON_INT,
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

@@ -514,7 +514,8 @@ public class PageObject {
}
protected void waitUntil(Function<WebDriver, Boolean> predicate) {
WebDriverWait webDriverWait = new WebDriverWait(driver, Duration.ofSeconds(DEFAULT_LOOKUP_TIMEOUT));
final WebDriverWait webDriverWait = new WebDriverWait(driver, Duration.ofSeconds(DEFAULT_LOOKUP_TIMEOUT));
webDriverWait.ignoring(StaleElementReferenceException.class);
webDriverWait.until(predicate);
}
@@ -171,7 +171,7 @@ public class AdminConsolePage extends HostPageWithAuthentication {
public UserManagementPanelPO goToUserManagement() {
goToTab(ADVANCED_PARENT_LABEL, ADVANCED_TAB_PARENT_IDENTIFIER, true);
return new UserManagementPanelPO(this.driver,
goToTab(USER_MANAGEMENT_PANEL_TAB_LABEL, USER_MANAGEMENT_PANEL_TAB_IDENTIFIER, false));
goToTab(USER_MANAGEMENT_PANEL_TAB_LABEL, USER_MANAGEMENT_PANEL_TAB_IDENTIFIER, false), this);
}
public UserGroupManagementPanelPO goToUserGroupDefinitions() {
@@ -1,5 +1,8 @@
package com.sap.sailing.selenium.pages.adminconsole.advanced;
import java.util.ArrayList;
import java.util.List;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
@@ -60,6 +63,7 @@ public class IpBlocklistPanelPO extends PageArea {
public void refresh() {
refreshButton.click();
waitForAjaxRequests();
}
public boolean isIpInTable(final String ip) {
@@ -68,8 +72,51 @@ public class IpBlocklistPanelPO extends PageArea {
return wasFound;
}
/**
* Unblocks the given IP address and blocks until the server-side release is confirmed by the row disappearing from
* a freshly reloaded table. The unlock GWT-RPC does not register with the pending-AJAX tracker (it uses a plain,
* unmarked {@code AsyncCallback}), so {@link #waitForAjaxRequests()} cannot observe it and the removed row alone
* only proves the client-side row was dropped, not that the lock was released. Refreshing (which does register)
* and waiting for the row to be gone therefore ties completion to a server round-trip, preventing a subsequent
* user creation from racing an as-yet-unreleased lock.
*/
public void unblockIP(String ip) {
cellTable.getEntry(ip).select();
unlockButton.click();
waitUntil(() -> {
refresh();
return !isIpInTable(ip);
});
}
/**
* Returns the IP addresses currently shown in the blocklist table, exactly as rendered in the "IP Address" column
* (including any formatting such as brackets around an IPv6 literal). This avoids guessing the textual form under
* which the server recorded a lock: the returned strings round-trip through {@link #unblockIP(String)} because they
* are the very identifiers {@link IPLockEntry#getIdentifier()} compares against. The table is refreshed first so
* that a lock recorded by a preceding operation is reflected before it is read.
*/
public List<String> getBlockedIps() {
refresh();
final List<String> result = new ArrayList<>();
for (final IPLockEntry entry : cellTable.getEntries()) {
result.add(entry.getIp());
}
return result;
}
/**
* Removes every lock currently present in the blocklist table by unblocking each shown IP address. Because a
* successful unblock removes the corresponding row and thereby invalidates the previously resolved page objects,
* the set of present IPs is read once up front via {@link #getBlockedIps()} and each address is then unblocked by
* re-resolving it against the (refreshed) table. Addresses that are no longer present when their turn comes are
* skipped, so this is safe even if an unblock removes more than the targeted row.
*/
public void unblockAllPresentIps() {
for (final String ip : getBlockedIps()) {
if (isIpInTable(ip)) {
unblockIP(ip);
}
}
}
}
@@ -62,6 +62,12 @@ public class RoleDefinitionsPanelPO extends PageArea {
}
return null;
}
public RoleEntryPO findRoleWhenPresent(final String roleName) {
waitForAjaxRequests();
waitUntil(() -> findRole(roleName) != null);
return findRole(roleName);
}
public void selectRole(String name) {
final CellTablePO<RoleEntryPO> table = getRoleTable();
@@ -20,6 +20,7 @@ public class AclActionInputPO extends PageArea {
}
public void addAction(String name) {
waitUntil(() -> inputSuggestBox.isDisplayed() && inputSuggestBox.isEnabled());
SuggestBoxPO.create(driver, inputSuggestBox).appendText(name);
addButton.click();
}
@@ -13,6 +13,8 @@ import com.sap.sailing.selenium.core.BySeleniumId;
import com.sap.sailing.selenium.core.FindBy;
import com.sap.sailing.selenium.pages.PageArea;
import com.sap.sailing.selenium.pages.adminconsole.ActionsHelper;
import com.sap.sailing.selenium.pages.adminconsole.AdminConsolePage;
import com.sap.sailing.selenium.pages.adminconsole.advanced.LocalServerPO;
import com.sap.sailing.selenium.pages.gwt.CellTablePO;
import com.sap.sailing.selenium.pages.gwt.DataEntryPO;
import com.sap.sailing.selenium.pages.gwt.GenericCellTablePO;
@@ -43,8 +45,15 @@ public class UserManagementPanelPO extends PageArea {
private TimePoint lastCreateUser;
private final AdminConsolePage adminConsole;
public UserManagementPanelPO(WebDriver driver, WebElement element) {
this(driver, element, null);
}
public UserManagementPanelPO(WebDriver driver, WebElement element, AdminConsolePage adminConsole) {
super(driver, element);
this.adminConsole = adminConsole;
}
private CellTablePO<DataEntryPO> getUserTable() {
@@ -92,17 +101,10 @@ public class UserManagementPanelPO extends PageArea {
}
public CreateUserDialogPO getCreateUserDialog() {
final TimePoint now = TimePoint.now();
if (lastCreateUser != null && now.minus(SecurityService.DEFAULT_CLIENT_IP_BASED_USER_CREATION_LOCKING_DURATION).before(lastCreateUser)) {
try {
final long sleepTimeMillis = now.until(lastCreateUser.plus(SecurityService.DEFAULT_CLIENT_IP_BASED_USER_CREATION_LOCKING_DURATION)).asMillis();
logger.info("Waiting "+sleepTimeMillis+"ms to create next user in "+this);
Thread.sleep(sleepTimeMillis);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (adminConsole != null) {
clearUserCreationLockForLocalIp();
} else {
logger.info("No wait required in dialog "+this);
throttleClientSideForUserCreationLock();
}
createUserButton.click();
final WebElement dialog = findElementBySeleniumId(this.driver, "CreateUserDialog");
@@ -114,6 +116,42 @@ public class UserManagementPanelPO extends PageArea {
}
};
}
/**
* Clears the server-side per-IP user-creation lock for the local client via the admin console's "User Creation
* Abuse" blocklist panel, so a subsequent user creation is not rejected by the rate limit. The server keys the lock
* by the connection's remote address, whose textual form for a loopback client depends on the stack (an IPv4 form
* such as {@code 127.0.0.1} or an IPv6 loopback form such as {@code 0:0:0:0:0:0:0:1} or {@code ::1}) and may be
* rendered with additional formatting in the table. Rather than guessing that literal, every currently locked
* address shown in the panel is unblocked. This requires navigating to the Local Server panel and back to User
* Management; the excursion is self-contained and leaves the User Management tab selected again for the caller.
*/
private void clearUserCreationLockForLocalIp() {
final LocalServerPO localServer = adminConsole.goToLocalServerPanel();
localServer.getUserCreationAbusePO().unblockAllPresentIps();
adminConsole.goToUserManagement();
}
/**
* Legacy fallback used when this panel was created without an {@link AdminConsolePage} reference and therefore
* cannot reach the blocklist panel to clear the server-side lock: pre-emptively sleeps for the remainder of the
* {@link SecurityService#DEFAULT_CLIENT_IP_BASED_USER_CREATION_LOCKING_DURATION} window since the last successful
* creation so the server IP lock is never hit.
*/
private void throttleClientSideForUserCreationLock() {
final TimePoint now = TimePoint.now();
if (lastCreateUser != null && now.minus(SecurityService.DEFAULT_CLIENT_IP_BASED_USER_CREATION_LOCKING_DURATION).before(lastCreateUser)) {
try {
final long sleepTimeMillis = now.until(lastCreateUser.plus(SecurityService.DEFAULT_CLIENT_IP_BASED_USER_CREATION_LOCKING_DURATION)).asMillis();
logger.info("Waiting "+sleepTimeMillis+"ms to create next user in "+this);
Thread.sleep(sleepTimeMillis);
} catch (final InterruptedException e) {
throw new RuntimeException(e);
}
} else {
logger.info("No wait required in dialog "+this);
}
}
public void createUserWithEqualUsernameAndPassword(String usernameAndPassword) {
logger.info("Starting process to create user "+usernameAndPassword);
@@ -28,10 +28,11 @@ public class UserRoleDefinitionPanelPO extends PageArea {
public void deleteRole() {
deleteButton.click();
waitForAjaxRequests();
}
public void deleteRoleAndExpectPermissionError() {
deleteRole();
deleteButton.click();
waitForAlertContainingMessageAndAccept("You are not allowed to revoke this role from user");
}
}
@@ -27,10 +27,11 @@ public class WildcardPermissionPanelPO extends PageArea {
public void deletePermission() {
deleteButton.click();
waitForAjaxRequests();
}
public void deletePermissionAndExpectPermissionError() {
deletePermission();
deleteButton.click();
waitForAlertContainingMessageAndAccept("Could not remove permission");
}
}
@@ -65,6 +66,7 @@ public class WildcardPermissionPanelPO extends PageArea {
public void addPermission(String permissionName) {
enterNewPermissionValue(permissionName);
clickAddButtonOrThrow();
waitForAjaxRequests();
waitUntil(() -> findPermission(permissionName) != null);
}
@@ -10,6 +10,7 @@ import java.util.function.Supplier;
import java.util.stream.Stream;
import org.openqa.selenium.By;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
@@ -175,11 +176,22 @@ public abstract class CellTablePO<T extends DataEntryPO> extends PageArea {
}
public List<T> getEntries() {
final List<T> entries = new ArrayList<>();
for (WebElement row : getRows()) {
entries.add(createDataEntry(row));
final int maxAttempts = 5;
List<T> result = null;
for (int attempt = 0; result == null && attempt < maxAttempts; attempt++) {
try {
final List<T> entries = new ArrayList<>();
for (final WebElement row : getRows()) {
entries.add(createDataEntry(row));
}
result = entries;
} catch (final StaleElementReferenceException e) {
if (attempt == maxAttempts - 1) {
throw e;
}
}
}
return entries;
return result;
}
public T getEntry(Object identifier) {
@@ -1,11 +1,13 @@
package com.sap.sailing.selenium.pages.gwt;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
@@ -72,24 +74,40 @@ public class GenericCellTablePO<T extends DataEntryPO> extends CellTablePO<T> {
@Override
public <S extends CellTablePO<T>> T createEntry(S table, WebElement element) {
Class<?> clazz = table.getClass();
T result = null;
final List<Exception> exceptionsCaught = new ArrayList<>();
while (clazz != null) {
while (clazz != null && result == null) {
try {
Constructor<T> constructor = this.type.getConstructor(clazz, WebElement.class);
return constructor.newInstance(table, element);
} catch (Exception exception) {
final Constructor<T> constructor = this.type.getConstructor(clazz, WebElement.class);
result = constructor.newInstance(table, element);
} catch (final NoSuchMethodException exception) {
clazz = clazz.getSuperclass();
exceptionsCaught.add(exception);
} catch (final InvocationTargetException exception) {
if (exception.getCause() instanceof StaleElementReferenceException) {
throw (StaleElementReferenceException) exception.getCause();
} else {
throw new RuntimeException("Can't create DataEntry of type " + this.type
+ " on table of type " + table.getClass().getName() + " on web element " + element,
exception);
}
} catch (final InstantiationException | IllegalAccessException exception) {
throw new RuntimeException("Can't create DataEntry of type " + this.type + " on table of type "
+ table.getClass().getName() + " on web element " + element, exception);
}
}
logger.warning("Unable to construct a DataEntryPO of type " + this.type.getName() + " for table " + table
+ " of type " + (table == null ? null : table.getClass().getName()) + " from web element "
+ element);
for (final Exception e : exceptionsCaught) {
logger.log(Level.WARNING, "Exception caught while trying to create a DataEntryPO of type "+this.type.getName(), e);
if (result == null) {
logger.warning("Unable to construct a DataEntryPO of type " + this.type.getName() + " for table "
+ table + " of type " + (table == null ? null : table.getClass().getName())
+ " from web element " + element);
for (final Exception e : exceptionsCaught) {
logger.log(Level.WARNING,
"Exception caught while trying to create a DataEntryPO of type " + this.type.getName(), e);
}
throw new RuntimeException("Can't create DataEntry of type " + this.type + " on table of type "
+ table.getClass().getName() + " on web element " + element);
}
throw new RuntimeException("Can't create DataEntry of type " + this.type + " on table of type "
+ table.getClass().getName() + " on web element " + element);
return result;
}
}
@@ -339,7 +339,7 @@ public class TestNegativeAcls extends AbstractSeleniumTest {
RoleDefinitionCreationAndUpdateDialogPO createRoleDialog = roleDefinitions.getCreateRoleDialog();
createRoleDialog.setName(CUSTOM_ROLE);
createRoleDialog.clickOkButtonOrThrow();
AclPopupPO aclPopup = roleDefinitions.findRole(CUSTOM_ROLE).openAclPopup();
AclPopupPO aclPopup = roleDefinitions.findRoleWhenPresent(CUSTOM_ROLE).openAclPopup();
aclPopup.addUserGroup("");
AclActionInputPO allowedActionsInput = aclPopup.getAllowedActionsInput();
// adding this ACL ensures that other users may read and update the custom role
@@ -25,6 +25,7 @@
<div class="innerContent">
<h2 class="articleSubheadline">September 2026</h2>
<ul class="bulletList">
<li>Added boat classes Onyx, Lacustre and 15m&sup2; SNS.</li>
<li>Bug fix: when allowing for competitor master data updates coming from TracTrac and the updates
were only about competitor nationality changes, those updates were not properly applied in all
cases. This is now fixed. <a href="https://github.com/eclipse-sailing-analytics/sailing-analytics/issues/6301">#6301</a></li>
@@ -32,6 +33,9 @@
<li>Warn landscape administrators when trying to remove or make read-only a replica set that
currently has live events
(see <a href="https://github.com/eclipse-sailing-analytics/sailing-analytics/issues/6288">issue #6288</a>)<li>
<li>Bug fix: when loading a replica for a primary that already has a non-empty CORS configuration for its replica set,
this CORS configuration was not properly applied on the replica, hence not serving the <tt>frame-ancestores</tt>
HTTP response header correctly.</li>
</ul>
<h2 class="articleSubheadline">August 2026</h2>
<ul class="bulletList">
@@ -34,6 +34,7 @@ import com.google.gwt.view.client.SetSelectionModel;
import com.sap.sse.common.Util;
import com.sap.sse.common.util.NaturalComparator;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.gwt.client.celltable.AbstractSortableTextColumn;
import com.sap.sse.gwt.client.celltable.CellTableWithCheckboxResources;
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
@@ -126,7 +127,7 @@ public class RoleDefinitionsPanel extends VerticalPanel {
@Override
public void ok(RoleDefinitionDTO editedObject) {
userManagementWriteService.createRoleDefinition(editedObject.getId().toString(),
editedObject.getName(), new AsyncCallback<RoleDefinitionDTO>() {
editedObject.getName(), new MarkedAsyncCallback<RoleDefinitionDTO>(new AsyncCallback<RoleDefinitionDTO>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError(stringMessages.errorCreatingRole(editedObject.getName(), caught.getMessage()));
@@ -146,7 +147,7 @@ public class RoleDefinitionsPanel extends VerticalPanel {
}
});
}
});
}));
}
@Override
@@ -7,6 +7,7 @@ import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.MultiSelectionModel;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.gwt.client.celltable.CellTableWithCheckboxResources;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
import com.sap.sse.security.shared.dto.RolesAndPermissionsForUserDTO;
@@ -41,7 +42,8 @@ public class EditUserRolesAndPermissionsDialog extends DataEntryDialog<Void> {
public void run() {
selectionAdapter.clear();
userService.getUserManagementService().getRolesAndPermissionsForUser(selectedUsername,
new AsyncCallback<RolesAndPermissionsForUserDTO>() {
new MarkedAsyncCallback<RolesAndPermissionsForUserDTO>(
new AsyncCallback<RolesAndPermissionsForUserDTO>() {
@Override
public void onSuccess(RolesAndPermissionsForUserDTO result) {
selectionAdapter.clear();
@@ -55,7 +57,7 @@ public class EditUserRolesAndPermissionsDialog extends DataEntryDialog<Void> {
public void onFailure(Throwable caught) {
errorReporter.reportError(caught.getMessage());
}
});
}));
}
};
wildcardPermissionPanel = new WildcardPermissionPanel(userService, stringMessages, errorReporter,
@@ -35,6 +35,7 @@ import com.sap.sse.gwt.client.DateAndTimeFormatterUtil;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.Notification;
import com.sap.sse.gwt.client.Notification.NotificationType;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.gwt.client.celltable.AbstractSortableTextColumn;
import com.sap.sse.gwt.client.celltable.CellTableWithCheckboxResources;
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
@@ -347,7 +348,7 @@ extends TableWrapper<UserDTO, S, StringMessages, TR> {
}
}
};
getUserManagementService().getUserList(myCallback);
getUserManagementService().getUserList(new MarkedAsyncCallback<>(myCallback));
}
public void refreshUserList(final Callback<Iterable<UserDTO>, Throwable> callback) {
@@ -25,6 +25,7 @@ import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SelectionChangeEvent.Handler;
import com.google.gwt.view.client.SingleSelectionModel;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.gwt.client.celltable.CellTableWithCheckboxResources;
import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel;
import com.sap.sse.security.shared.HasPermissions;
@@ -88,7 +89,7 @@ public class WildcardPermissionPanel extends HorizontalPanel
UserDTO selectedUser = this.userSelectionModel.getSelectedObject();
if (selectedUser != null) {
userService.getUserManagementWriteService().addPermissionForUser(selectedUser.getName(),
selectedPermission, new AsyncCallback<SuccessInfo>() {
selectedPermission, new MarkedAsyncCallback<SuccessInfo>(new AsyncCallback<SuccessInfo>() {
@Override
public void onFailure(Throwable caught) {
Window.alert(caught.getMessage());
@@ -102,7 +103,7 @@ public class WildcardPermissionPanel extends HorizontalPanel
Window.alert(result.getMessage());
}
}
});
}));
}
}
suggestPermission.setText("");
@@ -18,6 +18,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.view.client.SingleSelectionModel;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.gwt.client.celltable.AbstractSortableTextColumn;
import com.sap.sse.gwt.client.celltable.CellTableWithCheckboxResources;
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
@@ -78,7 +79,7 @@ public class WildcardPermissionWithSecurityDTOTableWrapper extends
UserDTO selectedObject = userSelectionModel.getSelectedObject();
if (selectedObject != null) {
userService.getUserManagementWriteService().removePermissionFromUser(selectedObject.getName(),
selectedPermission, new AsyncCallback<SuccessInfo>() {
selectedPermission, new MarkedAsyncCallback<SuccessInfo>(new AsyncCallback<SuccessInfo>() {
@Override
public void onFailure(Throwable caught) {
Window.alert(stringMessages.couldNotRemovePermissionFromUser(selectedObject.getName(),
@@ -94,7 +95,7 @@ public class WildcardPermissionWithSecurityDTOTableWrapper extends
Window.alert(result.getMessage());
}
}
});
}));
} else {
Window.alert(stringMessages.pleaseSelect());
}
@@ -17,6 +17,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.view.client.SingleSelectionModel;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.async.MarkedAsyncCallback;
import com.sap.sse.gwt.client.celltable.AbstractSortableTextColumn;
import com.sap.sse.gwt.client.celltable.CellTableWithCheckboxResources;
import com.sap.sse.gwt.client.celltable.EntityIdentityComparator;
@@ -83,7 +84,7 @@ public class RoleWithSecurityDTOTableWrapper extends
qualifiedForUser != null ? qualifiedForUser.getName() : null,
selectedRole.getRoleDefinition().getId(),
qualifiedForTenant == null ? null : qualifiedForTenant.getName(),
selectedRole.isTransitive(), new AsyncCallback<SuccessInfo>() {
selectedRole.isTransitive(), new MarkedAsyncCallback<SuccessInfo>(new AsyncCallback<SuccessInfo>() {
@Override
public void onFailure(Throwable caught) {
Window.alert(stringMessages.couldNotRemoveRoleFromUser(selectedObject.getName(),
@@ -99,7 +100,7 @@ public class RoleWithSecurityDTOTableWrapper extends
Window.alert(result.getMessage());
}
}
});
}));
} else {
Window.alert(stringMessages.pleaseSelect());
}
@@ -364,6 +364,7 @@ implements ReplicableSecurityService, ClearStateTestSupport {
this.hasPermissionsProvider = hasPermissionsProvider;
this.cacheManager = loadReplicationCacheManagerContents();
this.corsFilterConfigurationsByReplicaSetName = loadCORSFilterConfigurations();
applyCORSFilterForLocalReplicaSet();
logger.info("Loaded shiro.ini file from: classpath:shiro.ini");
final StringBuilder logMessage = new StringBuilder("[urls] section from Shiro configuration:");
final Section urlsSection = shiroConfiguration.getSection("urls");
@@ -428,15 +429,18 @@ implements ReplicableSecurityService, ClearStateTestSupport {
logger.info("Loading CORS filter configurations");
final ConcurrentMap<String, Pair<Boolean, Set<String>>> result = new ConcurrentHashMap<>();
result.putAll(PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory().loadCORSFilterConfigurationsForReplicaSetNames());
if (result.containsKey(ServerInfo.getName())) {
final Pair<Boolean, Set<String>> thisServersCORSFilterConfig = result.get(ServerInfo.getName());
return result;
}
private void applyCORSFilterForLocalReplicaSet() {
if (corsFilterConfigurationsByReplicaSetName.containsKey(ServerInfo.getName())) {
final Pair<Boolean, Set<String>> thisServersCORSFilterConfig = corsFilterConfigurationsByReplicaSetName.get(ServerInfo.getName());
if (thisServersCORSFilterConfig.getA()) {
getCORSFilterConfiguration().setWildcard();
} else {
getCORSFilterConfiguration().setOrigins(thisServersCORSFilterConfig.getB());
}
}
return result;
}
@Override
@@ -2665,6 +2669,7 @@ implements ReplicableSecurityService, ClearStateTestSupport {
final SecurityServiceInitialLoadExtensionsDTO initialLoadExtensions = (SecurityServiceInitialLoadExtensionsDTO) is.readObject();
final ConcurrentMap<String, Pair<Boolean, Set<String>>> newCORSFilterConfigurations = initialLoadExtensions.getCorsFilterConfigurationsByReplicaSetName();
corsFilterConfigurationsByReplicaSetName.putAll(newCORSFilterConfigurations);
applyCORSFilterForLocalReplicaSet();
if (initialLoadExtensions.getClientIPBasedTimedLocksForBearerTokenAuthentication() != null) {
// checking for null for backward compatibility; an older primary/master may not have known this field yet
clientIPBasedTimedLocksForBearerTokenAuthentication.putAll(initialLoadExtensions.getClientIPBasedTimedLocksForBearerTokenAuthentication());