bug6060: handle BrandingConfiguration service appearance/disappearance dynamically

This commit is contained in:
Axel Uhl committed 2025-08-15 18:15:36 +02:00
1 parent 9a5a4e091c
commit 4964ec9410
5 files changed
+127 -49

No files matched your search

@@ -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 {
* <p>
* @param locale if present, the locale to use for the branding configuration properties, otherwise the default locale
*/
Map<BrandingConfigurationProperty, Object> getBrandingConfigurationPropertiesForJspContext(Optional<String> locale);
Map<BrandingConfigurationProperty, Object> getBrandingConfigurationProperties(Optional<String> locale);
/**
* Same as {@link #getBrandingConfigurationProperties(Optional)} but returns a map where the keys are
* the property names as defined in {@link BrandingConfigurationProperty#getPropertyName()}.
*/
Map<String, Object> getBrandingConfigurationPropertiesForJspContext(Optional<String> locale);
}
@@ -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
* <p>
*
* 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.
* <p>
@@ -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<String> requestLocale = GwtLocaleFromHttpRequestUtil.getLocaleFromHttpRequest(httpServletRequest);
final Map<String, Object> brandingProperties = getAndCacheBrandingConfigurationPropertiesFromServletContext(
servletContext, requestLocale, Activator.getDefaultBrandingConfigurationService());
final Map<String, Object> brandingProperties = Activator.getDefaultBrandingConfigurationService().getBrandingConfigurationPropertiesForJspContext(requestLocale);
httpServletRequest.setAttribute(BrandingConfigurationService.JSP_PROPERTY_NAME_PREFIX, brandingProperties);
}
}
}
private Map<String, Object> getAndCacheBrandingConfigurationPropertiesFromServletContext(
ServletContext servletContext, Optional<String> locale, BrandingConfigurationService brandingConfigurationService) {
@SuppressWarnings("unchecked")
ConcurrentMap<Pair<String, String>, Map<String, Object>> brandingProperties =
(ConcurrentMap<Pair<String, String>, Map<String, Object>>) 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<String, String> key = new Pair<>(locale.orElse(null),
brandingConfigurationService.isBrandingActive() ? brandingConfigurationService.getActiveBrandingConfiguration().getId() : null);
return brandingProperties.computeIfAbsent(key, k->computeBrandingConfigurationProperties(brandingConfigurationService, locale));
}
private Map<String, Object> computeBrandingConfigurationProperties(BrandingConfigurationService brandingConfigurationService, Optional<String> requestLocale) {
final Map<String, Object> 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
@@ -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<BrandingConfiguration, BrandingConfiguration> brandingConfigurationTracker;
public class BrandingConfigurationServiceImpl implements BrandingConfigurationService, ServiceTrackerCustomizer<BrandingConfiguration, BrandingConfiguration> {
private static final Logger logger = Logger.getLogger(BrandingConfigurationServiceImpl.class.getName());
private final ServiceTracker<BrandingConfiguration, BrandingConfiguration> brandingConfigurationTracker;
private final BundleContext bundleContext;
private final ConcurrentMap<Pair<String, String>, Map<String, Object>> 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<BrandingConfigurationProperty, Object> getBrandingConfigurationPropertiesForJspContext(Optional<String> locale) {
public Map<BrandingConfigurationProperty, Object> getBrandingConfigurationProperties(Optional<String> locale) {
final BrandingConfiguration brandingConfiguration = getActiveBrandingConfiguration();
final Map<BrandingConfigurationProperty, Object> 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<String, Object> getBrandingConfigurationPropertiesForJspContext(Optional<String> locale) {
return brandingConfigurationsByIdAndLocale.computeIfAbsent(
new Pair<>(locale.orElse(null), isBrandingActive() ? getActiveBrandingConfiguration().getId() : null),
key -> computeBrandingConfigurationProperties(locale));
}
private Map<String, Object> computeBrandingConfigurationProperties(Optional<String> requestLocale) {
final Map<String, Object> 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<BrandingConfigurationProperty, Object> 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<BrandingConfiguration> 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<BrandingConfiguration> reference) {
final String brandingId = (String) reference.getProperty(BrandingConfigurationService.BRANDING_ID_PROPERTY_NAME);
final BrandingConfiguration service = bundleContext.getService(reference);
for (final Iterator<Pair<String, String>> i=brandingConfigurationsByIdAndLocale.keySet().iterator(); i.hasNext(); ) {
final Pair<String, String> 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<BrandingConfiguration> 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<BrandingConfiguration> 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;
}
}
}
@@ -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",
@@ -20,7 +20,7 @@ import com.sap.sse.gwt.client.context.impl.ClientConfigurationContextDataFactory
* document.clientConfigurationContext={"brandingActive":true,"brandTitle":"SAP ","debrandingActive":false,"whitelabeled":""};
* </pre>
* 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.<p>
*