From 4964ec9410ad9719a8afe5070a476ff58aa4259c Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 15 Aug 2025 18:15:36 +0200 Subject: [PATCH] bug6060: handle BrandingConfiguration service appearance/disappearance dynamically --- .../BrandingConfigurationService.java | 17 +-- .../branding/ClientConfigurationListener.java | 32 +---- .../BrandingConfigurationServiceImpl.java | 124 ++++++++++++++++-- java/com.sap.sse.gwt/META-INF/MANIFEST.MF | 1 + .../sse/gwt/shared/ClientConfiguration.java | 2 +- 5 files changed, 127 insertions(+), 49 deletions(-) diff --git a/java/com.sap.sse.branding/src/com/sap/sse/branding/BrandingConfigurationService.java b/java/com.sap.sse.branding/src/com/sap/sse/branding/BrandingConfigurationService.java index 985fb81280f..3fc687c17f3 100644 --- a/java/com.sap.sse.branding/src/com/sap/sse/branding/BrandingConfigurationService.java +++ b/java/com.sap.sse.branding/src/com/sap/sse/branding/BrandingConfigurationService.java @@ -3,8 +3,6 @@ package com.sap.sse.branding; import java.util.Map; import java.util.Optional; -import javax.servlet.ServletContext; - import com.sap.sse.branding.shared.BrandingConfiguration; /** @@ -49,13 +47,6 @@ public interface BrandingConfigurationService { */ String JSP_PROPERTY_NAME_PREFIX = "clientConfigurationContext"; - /** - * Attribute name used in the {@link ServletContext}, value being a map from a pair of locale (e.g. "en") and - * branding ID (see {@link BrandingConfiguration#getId()}) to a map of JSP properties that can be used to set it - * to the {@link #JSP_PROPERTY_NAME_PREFIX} attribute in the {@link HttpServletRequest} scope. - */ - String JSP_PROPERTIES_BY_LOCALE_AND_BRANDING_ID = "clientConfigurationContextsByLocaleAndBrandingId"; - public static enum BrandingConfigurationProperty { /** * The name of the JSP property, appended to the value of {@link #JSP_PROPERTY_NAME_PREFIX}, that contains the brand title @@ -134,5 +125,11 @@ public interface BrandingConfigurationService { *

* @param locale if present, the locale to use for the branding configuration properties, otherwise the default locale */ - Map getBrandingConfigurationPropertiesForJspContext(Optional locale); + Map getBrandingConfigurationProperties(Optional locale); + + /** + * Same as {@link #getBrandingConfigurationProperties(Optional)} but returns a map where the keys are + * the property names as defined in {@link BrandingConfigurationProperty#getPropertyName()}. + */ + Map getBrandingConfigurationPropertiesForJspContext(Optional locale); } diff --git a/java/com.sap.sse.branding/src/com/sap/sse/branding/ClientConfigurationListener.java b/java/com.sap.sse.branding/src/com/sap/sse/branding/ClientConfigurationListener.java index e36c72c94f0..aa2bb90f1db 100644 --- a/java/com.sap.sse.branding/src/com/sap/sse/branding/ClientConfigurationListener.java +++ b/java/com.sap.sse.branding/src/com/sap/sse/branding/ClientConfigurationListener.java @@ -1,10 +1,7 @@ package com.sap.sse.branding; -import java.util.HashMap; import java.util.Map; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import javax.servlet.ServletContext; import javax.servlet.ServletRequestEvent; @@ -12,7 +9,6 @@ import javax.servlet.http.HttpServletRequest; import com.sap.sse.branding.BrandingConfigurationService.BrandingConfigurationProperty; import com.sap.sse.branding.impl.Activator; -import com.sap.sse.common.Util.Pair; import com.sap.sse.rest.GwtLocaleFromHttpRequestUtil; /** @@ -71,7 +67,7 @@ public class ClientConfigurationListener implements javax.servlet.ServletRequest *

* * The properties are constructed using - * {@link BrandingConfigurationService#getBrandingConfigurationPropertiesForJspContext(Optional)} with the locale as + * {@link BrandingConfigurationService#getBrandingConfigurationProperties(Optional)} with the locale as * argument, where the property names are derived from the {@link BrandingConfigurationProperty#getPropertyName()} * method which get appended to the {@link BrandingConfigurationService#JSP_PROPERTY_NAME_PREFIX} prefix. *

@@ -86,37 +82,13 @@ public class ClientConfigurationListener implements javax.servlet.ServletRequest final String path = httpServletRequest.getServletPath(); // populate JSP scopes (applicationScope and requestScope) only for HTML pages or "/" requests if (path != null && (path.endsWith("/") || path.endsWith(".html"))) { - final ServletContext servletContext = httpServletRequest.getServletContext(); Optional requestLocale = GwtLocaleFromHttpRequestUtil.getLocaleFromHttpRequest(httpServletRequest); - final Map brandingProperties = getAndCacheBrandingConfigurationPropertiesFromServletContext( - servletContext, requestLocale, Activator.getDefaultBrandingConfigurationService()); + final Map brandingProperties = Activator.getDefaultBrandingConfigurationService().getBrandingConfigurationPropertiesForJspContext(requestLocale); httpServletRequest.setAttribute(BrandingConfigurationService.JSP_PROPERTY_NAME_PREFIX, brandingProperties); } } } - private Map getAndCacheBrandingConfigurationPropertiesFromServletContext( - ServletContext servletContext, Optional locale, BrandingConfigurationService brandingConfigurationService) { - @SuppressWarnings("unchecked") - ConcurrentMap, Map> brandingProperties = - (ConcurrentMap, Map>) servletContext.getAttribute(BrandingConfigurationService.JSP_PROPERTIES_BY_LOCALE_AND_BRANDING_ID); - if (brandingProperties == null) { - brandingProperties = new ConcurrentHashMap<>(); - servletContext.setAttribute(BrandingConfigurationService.JSP_PROPERTIES_BY_LOCALE_AND_BRANDING_ID, brandingProperties); - } - final Pair key = new Pair<>(locale.orElse(null), - brandingConfigurationService.isBrandingActive() ? brandingConfigurationService.getActiveBrandingConfiguration().getId() : null); - return brandingProperties.computeIfAbsent(key, k->computeBrandingConfigurationProperties(brandingConfigurationService, locale)); - } - - private Map computeBrandingConfigurationProperties(BrandingConfigurationService brandingConfigurationService, Optional requestLocale) { - final Map brandingProperties = new HashMap<>(); // no concurrency control needed within single request - brandingConfigurationService.getBrandingConfigurationPropertiesForJspContext(requestLocale).forEach((k, v) -> { - brandingProperties.put(k.getPropertyName(), v); - }); - return brandingProperties; - } - @Override public void requestDestroyed(ServletRequestEvent sre) { // intentionally left blank diff --git a/java/com.sap.sse.branding/src/com/sap/sse/branding/impl/BrandingConfigurationServiceImpl.java b/java/com.sap.sse.branding/src/com/sap/sse/branding/impl/BrandingConfigurationServiceImpl.java index 6d565e2dc45..8eacbaacd91 100644 --- a/java/com.sap.sse.branding/src/com/sap/sse/branding/impl/BrandingConfigurationServiceImpl.java +++ b/java/com.sap.sse.branding/src/com/sap/sse/branding/impl/BrandingConfigurationServiceImpl.java @@ -1,29 +1,54 @@ package com.sap.sse.branding.impl; +import java.util.Arrays; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.logging.Logger; import org.json.simple.JSONObject; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.Filter; import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; import com.sap.sse.branding.BrandingConfigurationService; import com.sap.sse.branding.shared.BrandingConfiguration; +import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; import com.sap.sse.util.ServiceTrackerFactory; -public class BrandingConfigurationServiceImpl implements BrandingConfigurationService { - private ServiceTracker brandingConfigurationTracker; +public class BrandingConfigurationServiceImpl implements BrandingConfigurationService, ServiceTrackerCustomizer { + private static final Logger logger = Logger.getLogger(BrandingConfigurationServiceImpl.class.getName()); + + private final ServiceTracker brandingConfigurationTracker; private final BundleContext bundleContext; + private final ConcurrentMap, Map> brandingConfigurationsByIdAndLocale; + + /** + * Starts out as {@code null}. Will be set by {@link #setActiveBrandingConfigurationById(String)} if the + * configuration by that ID is found through the {@link #brandingConfigurationTracker}. Furthermore, + * a {@link ServiceTrackerCustomizer} is used to update this field whenever the service currently in + * use is removed or modified, or a new service matching the ID is added. + */ + private BrandingConfiguration activeBrandingConfiguration; + + private Filter filterForActiveBrandingConfigurationId; + public BrandingConfigurationServiceImpl(BundleContext bundleContext) { super(); this.bundleContext = bundleContext; + brandingConfigurationTracker = ServiceTrackerFactory.createAndOpen(bundleContext, BrandingConfiguration.class, this); + brandingConfigurationsByIdAndLocale = new ConcurrentHashMap<>(); } @Override @@ -33,18 +58,35 @@ public class BrandingConfigurationServiceImpl implements BrandingConfigurationSe @Override public BrandingConfiguration setActiveBrandingConfigurationById(String brandingConfigurationId) { + filterForActiveBrandingConfigurationId = createFilterForBrandingConfigurationId(brandingConfigurationId); + activeBrandingConfiguration = brandingConfigurationTracker.getServiceReferences() == null ? null : + Arrays.asList(brandingConfigurationTracker.getServiceReferences()) + .stream() + .filter(ref -> filterForActiveBrandingConfigurationId.match(ref.getProperties())) + .findFirst() + .map(ref -> brandingConfigurationTracker.getService(ref)) + .orElse(null); + if (activeBrandingConfiguration == null) { + logger.warning("Couldn't find a branding configuration with ID " + brandingConfigurationId + + " in the OSGi service registry. Branding is effectively deactivated."); + } else { + logger.info("Found active branding configuration: " + activeBrandingConfiguration.getId()+ + " with object ID "+System.identityHashCode(activeBrandingConfiguration)); + } + return activeBrandingConfiguration; + } + + private Filter createFilterForBrandingConfigurationId(String brandingConfigurationId) { try { - Filter filter = bundleContext.createFilter( + return bundleContext.createFilter( String.format("(&(%s=%s)(%s=%s))", BrandingConfigurationService.BRANDING_ID_PROPERTY_NAME, ""+brandingConfigurationId, Constants.OBJECTCLASS, BrandingConfiguration.class.getName())); - brandingConfigurationTracker = ServiceTrackerFactory.createAndOpen(bundleContext, filter); - return brandingConfigurationTracker.getService(); } catch (InvalidSyntaxException e) { throw new RuntimeException("Internal error: Invalid filter syntax for BrandingConfigurationService", e); } } - + @Override public BrandingConfiguration getActiveBrandingConfiguration() { return brandingConfigurationTracker == null ? null : brandingConfigurationTracker.getService(); @@ -52,7 +94,7 @@ public class BrandingConfigurationServiceImpl implements BrandingConfigurationSe @Override - public Map getBrandingConfigurationPropertiesForJspContext(Optional locale) { + public Map getBrandingConfigurationProperties(Optional locale) { final BrandingConfiguration brandingConfiguration = getActiveBrandingConfiguration(); final Map map = new HashMap<>(); final String title; @@ -64,7 +106,6 @@ public class BrandingConfigurationServiceImpl implements BrandingConfigurationSe title = ""; whitelabeled = "-whitelabeled"; } - // TODO bug6060: figure out how to deal with i18n and the locale parameter map.put(BrandingConfigurationProperty.BRAND_TITLE_WITH_TRAILING_SPACE_JSP_PROPERTY_NAME, title); map.put(BrandingConfigurationProperty.DEBRANDING_ACTIVE_JSP_PROPERTY_NAME, !isBrandingActive()); map.put(BrandingConfigurationProperty.BRANDING_ACTIVE_JSP_PROPERTY_NAME, isBrandingActive()); @@ -73,6 +114,21 @@ public class BrandingConfigurationServiceImpl implements BrandingConfigurationSe return map; } + @Override + public Map getBrandingConfigurationPropertiesForJspContext(Optional locale) { + return brandingConfigurationsByIdAndLocale.computeIfAbsent( + new Pair<>(locale.orElse(null), isBrandingActive() ? getActiveBrandingConfiguration().getId() : null), + key -> computeBrandingConfigurationProperties(locale)); + } + + private Map computeBrandingConfigurationProperties(Optional requestLocale) { + final Map brandingProperties = new HashMap<>(); // no concurrency control needed within single request + getBrandingConfigurationProperties(requestLocale).forEach((k, v) -> { + brandingProperties.put(k.getPropertyName(), v); + }); + return brandingProperties; + } + private Object generateScriptForClientConfigurationContext(Map map) { final StringBuilder scriptBuilder = new StringBuilder(); scriptBuilder.append("document.clientConfigurationContext="); @@ -84,4 +140,56 @@ public class BrandingConfigurationServiceImpl implements BrandingConfigurationSe scriptBuilder.append(";"); return scriptBuilder.toString(); } + + @Override + public BrandingConfiguration addingService(ServiceReference reference) { + final BrandingConfiguration service = evictCachedPropertiesForBrandingServiceReference(reference); + logger.info("Adding branding configuration service with ID: " + reference.getProperty(BrandingConfigurationService.BRANDING_ID_PROPERTY_NAME) + + " and object ID " + System.identityHashCode(service)); + if (service != null && filterForActiveBrandingConfigurationId.match(reference)) { + logger.info("Added branding configuration service with ID " + reference.getProperty(BrandingConfigurationService.BRANDING_ID_PROPERTY_NAME) + + " is selected as the active one."); + activeBrandingConfiguration = service; + } + return service; + } + + private BrandingConfiguration evictCachedPropertiesForBrandingServiceReference( + ServiceReference reference) { + final String brandingId = (String) reference.getProperty(BrandingConfigurationService.BRANDING_ID_PROPERTY_NAME); + final BrandingConfiguration service = bundleContext.getService(reference); + for (final Iterator> i=brandingConfigurationsByIdAndLocale.keySet().iterator(); i.hasNext(); ) { + final Pair cachedProperties = i.next(); + if (Util.equalsWithNull(cachedProperties.getB(), brandingId)) { + logger.info("Removing cached branding properties from service with ID: " + brandingId + " and object ID " + System.identityHashCode(service)); + // remove the cached properties for this ID, so that they will be recomputed + i.remove(); + } + } + return service; + } + + @Override + public void modifiedService(ServiceReference reference, BrandingConfiguration service) { + evictCachedPropertiesForBrandingServiceReference(reference); + logger.info("Modified branding configuration service with ID: " + reference.getProperty(BrandingConfigurationService.BRANDING_ID_PROPERTY_NAME) + + " and object ID " + System.identityHashCode(service)); + if (service != null && filterForActiveBrandingConfigurationId.match(reference)) { + logger.info("Modified branding configuration service with ID " + reference.getProperty(BrandingConfigurationService.BRANDING_ID_PROPERTY_NAME) + + ", updating active branding configuration."); + activeBrandingConfiguration = service; + } + } + + @Override + public void removedService(ServiceReference reference, BrandingConfiguration service) { + evictCachedPropertiesForBrandingServiceReference(reference); + logger.info("Removed branding configuration service with ID: " + reference.getProperty(BrandingConfigurationService.BRANDING_ID_PROPERTY_NAME) + + " and object ID " + System.identityHashCode(service)); + if (service == activeBrandingConfiguration) { + logger.info("The active branding configuration service with ID " + reference.getProperty(BrandingConfigurationService.BRANDING_ID_PROPERTY_NAME) + + " was removed, setting active branding configuration to null."); + activeBrandingConfiguration = null; + } + } } diff --git a/java/com.sap.sse.gwt/META-INF/MANIFEST.MF b/java/com.sap.sse.gwt/META-INF/MANIFEST.MF index 3c6482ac93f..06ff4c51301 100644 --- a/java/com.sap.sse.gwt/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.gwt/META-INF/MANIFEST.MF @@ -16,6 +16,7 @@ Require-Bundle: com.google.gwt.user;bundle-version="2.10.0";resolution:=optional Bundle-ActivationPolicy: lazy Web-ContextPath: /gwt-base Import-Package: com.sap.sse.branding.shared, + com.sap.sse.security, com.sun.jersey.spi.container.servlet;version="[1.17.0,2.0.0)", javax.servlet;version="3.1.0", javax.servlet.descriptor;version="3.1.0", diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/shared/ClientConfiguration.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/shared/ClientConfiguration.java index fdc0978aaa0..2531882d1b9 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/shared/ClientConfiguration.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/shared/ClientConfiguration.java @@ -20,7 +20,7 @@ import com.sap.sse.gwt.client.context.impl.ClientConfigurationContextDataFactory * document.clientConfigurationContext={"brandingActive":true,"brandTitle":"SAP ","debrandingActive":false,"whitelabeled":""}; * * That can be accessed from here using the {@link ClientConfigurationContextDataJSO}. Responsible for the properties listed in the - * object is {@link BrandingConfigurationService#getBrandingConfigurationPropertiesForJspContext(Optional)} and its + * object is {@link BrandingConfigurationService#getBrandingConfigurationProperties(Optional)} and its * {@link BrandingConfigurationService#getActiveBrandingConfiguration() active branding configuration}, or none if the solution * is running without any branding.

*