Skip to content

Commit

Permalink
Custom Service Configurations availability logic
Browse files Browse the repository at this point in the history
Signed-off-by: Claudio Mezzasalma <claudio.mezzasalma@eurotech.com>
  • Loading branch information
Claudio Mezzasalma committed May 11, 2020
1 parent 3f95c1a commit 82b52df
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ protected int allowedChildEntities(KapuaId scopeId, KapuaId targetScopeId, Map<S
AccountFactory accountFactory = locator.getFactory(AccountFactory.class);
AccountService accountService = locator.getService(AccountService.class);

Map<String, Object> finalConfig = configuration == null ? getConfigValues(scopeId) : configuration;
Map<String, Object> finalConfig = configuration == null ? getConfigValues(scopeId, false) : configuration;
boolean allowInfiniteChildEntities = (boolean) finalConfig.get("infiniteChildEntities");
if (!allowInfiniteChildEntities) {
return KapuaSecurityUtils.doPrivileged(() -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.eclipse.kapua.model.id.KapuaId;
import org.eclipse.kapua.model.query.predicate.AndPredicate;
import org.eclipse.kapua.model.query.predicate.AttributePredicate.Operator;
import org.eclipse.kapua.service.account.Account;
import org.eclipse.kapua.service.authorization.AuthorizationService;
import org.eclipse.kapua.service.authorization.permission.PermissionFactory;
import org.eclipse.kapua.service.config.KapuaConfigurableService;
Expand All @@ -50,6 +51,9 @@
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Properties;
import java.util.stream.Collectors;

import org.apache.commons.lang3.tuple.Triple;

/**
* Configurable service definition abstract reference implementation.
Expand All @@ -62,8 +66,25 @@ public abstract class AbstractKapuaConfigurableService extends AbstractKapuaServ
SystemSetting.getInstance().getInt(SystemSettingKey.TMETADATA_LOCAL_CACHE_SIZE_MAXIMUM, 100);
private static final EntityCache PRIVATE_ENTITY_CACHE =
AbstractKapuaConfigurableServiceCache.getInstance().createCache();
private static final LocalCache<String, KapuaTmetadata> KAPUA_TMETADATA_LOCAL_CACHE =
/**
* This cache is to hold the KapuaTocd that are read from the metatype files.
* The key is a {@link Triple} composed by:
* <ul>
* <li>The service PID</li>
* <li>The ID of the {@link Account} for the current request</li>
* <li>A {@link Boolean} flag indicating whether unavailable properties are excluded from the AD or not</li>
* </ul>
*/
private static final LocalCache<Triple<String, KapuaId, Boolean>, KapuaTocd> KAPUA_TOCD_LOCAL_CACHE =
new LocalCache<>(SIZEMAX, null);
/**
* This cache only holds the {@link Boolean} value {@literal True} if the OCD has been already read from the file
* at least once, regardless of the value. With this we can know when a read from {@code KAPUA_TOCD_LOCAL_CACHE}
* returns {@literal null} becauseof the requested key is not present, and when the key is present but its actual value
* is {@literal null}.
*/
private static final LocalCache<Triple<String, KapuaId, Boolean>, Boolean> KAPUA_TOCD_EMPTY_LOCAL_CACHE =
new LocalCache<>(SIZEMAX, false);

protected AbstractKapuaConfigurableService(String pid, Domain domain, EntityManagerFactory entityManagerFactory) {
this(pid, domain, entityManagerFactory, null);
Expand Down Expand Up @@ -105,13 +126,20 @@ private void validateConfigurations(String pid, KapuaTocd ocd, Map<String, Objec
throws KapuaException {
if (ocd != null) {

// build a map of all the attribute definitions
Map<String, KapuaTad> attrDefs = new HashMap<>();
List<KapuaTad> defs = ocd.getAD();
for (KapuaTad def : defs) {
attrDefs.put(def.getId(), def);
// Get Unavailable Properties
List<KapuaTad> unavailableProperties = ocd.getAD().stream().filter(ad -> !isAvailableProperty(ad)).collect(Collectors.toList());

if (!unavailableProperties.isEmpty()) {
// If there's any unavailable property, read current values to overwrite the proposed ones
Map<String, Object> originalValues = getConfigValues(scopeId, false);
if (originalValues != null) {
unavailableProperties.forEach(unavailableProp -> updatedProps.put(unavailableProp.getId(), originalValues.get(unavailableProp.getId())));
}
}

// build a map of all the attribute definitions
Map<String, KapuaTad> attrDefs = ocd.getAD().stream().collect(Collectors.toMap(KapuaTad::getId, ad -> ad));

// loop over the proposed property values
// and validate them against the definition
for (Entry<String, Object> property : updatedProps.entrySet()) {
Expand Down Expand Up @@ -247,38 +275,66 @@ private ServiceConfig updateConfig(ServiceConfig serviceConfig)
}

@Override
public KapuaTocd getConfigMetadata(KapuaId scopeId)
throws KapuaException {
public KapuaTocd getConfigMetadata(KapuaId scopeId) throws KapuaException {
return getConfigMetadata(scopeId, true);
}

protected KapuaTocd getConfigMetadata(KapuaId scopeId, boolean excludeUnavailable) throws KapuaException {
KapuaLocator locator = KapuaLocator.getInstance();
AuthorizationService authorizationService = locator.getService(AuthorizationService.class);
PermissionFactory permissionFactory = locator.getFactory(PermissionFactory.class);

authorizationService.checkPermission(permissionFactory.newPermission(domain, Actions.read, scopeId));

// Keep distinct values for service PID, Scope ID and unavailable properties included/excluded from AD
Triple<String, KapuaId, Boolean> cacheKey = Triple.of(pid, scopeId, excludeUnavailable);
try {
KapuaTmetadata metadata = KAPUA_TMETADATA_LOCAL_CACHE.get(pid);
if (metadata == null) {
metadata = readMetadata(pid);
if (metadata != null) {
KAPUA_TMETADATA_LOCAL_CACHE.put(pid, metadata);
// Check if the OCD is already in cache, but not in the "empty" cache
KapuaTocd tocd = KAPUA_TOCD_LOCAL_CACHE.get(cacheKey);
if (tocd == null && !KAPUA_TOCD_EMPTY_LOCAL_CACHE.get(cacheKey)) {
// If not, read metadata and process it
tocd = processMetadata(readMetadata(pid), excludeUnavailable);
// If null, put it in the "empty" ocd cache, else put it in the "standard" cache
if (tocd != null) {
// If the value is not null, put it in "standard" cache and remove the entry from the "empty" cache if present
KAPUA_TOCD_LOCAL_CACHE.put(cacheKey, tocd);
KAPUA_TOCD_EMPTY_LOCAL_CACHE.remove(cacheKey);
} else {
// If the value is null, just remember we already read it from file at least once
KAPUA_TOCD_EMPTY_LOCAL_CACHE.put(cacheKey, true);
}
}
if (metadata != null && metadata.getOCD() != null && !metadata.getOCD().isEmpty()) {
for (KapuaTocd ocd : metadata.getOCD()) {
if (ocd.getId() != null && ocd.getId().equals(pid)) {
return ocd;
}
}
}
return null;
return tocd;
} catch (KapuaConfigurationException e) {
throw e;
} catch (Exception e) {
throw KapuaException.internalError(e);
}
}

/**
* Process metadata to exclude unavailable services and properties
*
* @param metadata A {@link KapuaTmetadata} object
* @param excludeUnavailable if {@literal true} exclude unavailable properties from the AD object
* @return The processed {@link KapuaTocd} object
*/
private KapuaTocd processMetadata(KapuaTmetadata metadata, boolean excludeUnavailable) {
if (metadata != null && metadata.getOCD() != null && !metadata.getOCD().isEmpty()) {
for (KapuaTocd ocd : metadata.getOCD()) {
if (ocd.getId() != null && ocd.getId().equals(pid) && isAvailableService()) {
ocd.getAD().removeIf(ad -> excludeUnavailable && !isAvailableProperty(ad));
return ocd;
}
}
}
return null;
}

@Override
public Map<String, Object> getConfigValues(KapuaId scopeId)
throws KapuaException {
public Map<String, Object> getConfigValues(KapuaId scopeId) throws KapuaException {
return getConfigValues(scopeId, true);
}

protected Map<String, Object> getConfigValues(KapuaId scopeId, boolean excludeUnavailable) throws KapuaException {
KapuaLocator locator = KapuaLocator.getInstance();
AuthorizationService authorizationService = locator.getService(AuthorizationService.class);
PermissionFactory permissionFactory = locator.getFactory(PermissionFactory.class);
Expand All @@ -294,15 +350,15 @@ public Map<String, Object> getConfigValues(KapuaId scopeId)
query.setPredicate(predicate);

ServiceConfigListResult result = entityManagerSession.doAction(EntityManagerContainer.<ServiceConfigListResult>create().onResultHandler(em -> ServiceDAO.query(em, ServiceConfig.class, ServiceConfigImpl.class, new ServiceConfigListResultImpl(), query))
.onBeforeHandler(() -> (ServiceConfigListResult) PRIVATE_ENTITY_CACHE.getList(scopeId, pid))
.onAfterHandler((entity) -> PRIVATE_ENTITY_CACHE.putList(scopeId, pid, entity)));
.onBeforeHandler(() -> (ServiceConfigListResult) PRIVATE_ENTITY_CACHE.getList(scopeId, pid))
.onAfterHandler(entity -> PRIVATE_ENTITY_CACHE.putList(scopeId, pid, entity)));

Properties properties = null;
if (result != null && !result.isEmpty()) {
properties = result.getFirstItem().getConfigurations();
}

KapuaTocd ocd = getConfigMetadata(scopeId);
KapuaTocd ocd = getConfigMetadata(scopeId, excludeUnavailable);
return ocd == null ? null : toValues(ocd, properties);
}

Expand All @@ -314,7 +370,7 @@ public void setConfigValues(KapuaId scopeId, KapuaId parentId, Map<String, Objec
PermissionFactory permissionFactory = locator.getFactory(PermissionFactory.class);
authorizationService.checkPermission(permissionFactory.newPermission(domain, Actions.write, scopeId));

KapuaTocd ocd = getConfigMetadata(scopeId);
KapuaTocd ocd = getConfigMetadata(scopeId, false);
validateConfigurations(pid, ocd, values, scopeId, parentId);

ServiceConfigQueryImpl query = new ServiceConfigQueryImpl(scopeId);
Expand Down Expand Up @@ -343,4 +399,13 @@ public void setConfigValues(KapuaId scopeId, KapuaId parentId, Map<String, Objec
updateConfig(serviceConfig);
}
}

protected boolean isAvailableService() {
return true;
}

protected boolean isAvailableProperty(KapuaTad ad) {
return true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,9 @@ public enum KapuaConfigurationErrorCodes implements KapuaErrorCode {
/**
* Parent limit exceeded in config
*/
PARENT_LIMIT_EXCEEDED_IN_CONFIG
PARENT_LIMIT_EXCEEDED_IN_CONFIG,
/**
* The service is not available
*/
SERVICE_UNAVAILABLE
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public void setId(String id) {
}

@Override
public void setIcon(List<? extends KapuaTicon> icon) {
public void setIcon(List<KapuaTicon> icon) {
// No OP implementation
}

Expand All @@ -64,7 +64,7 @@ public void setAny(List<Object> any) {
}

@Override
public void setAD(List<? extends KapuaTad> icon) {
public void setAD(List<KapuaTad> icon) {
// No OP implementation
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@
*/
public class TocdImpl implements KapuaTocd {

protected List<TadImpl> ad;
protected List<TiconImpl> icon;
protected List<KapuaTad> ad;
protected List<KapuaTicon> icon;
protected List<Object> any;
protected String name;
protected String description;
Expand Down Expand Up @@ -85,17 +85,12 @@ public List<KapuaTad> getAD() {
if (ad == null) {
ad = new ArrayList<>();
}
return new ArrayList<>(this.ad);
return this.ad;
}

@Override
public void setAD(List<? extends KapuaTad> ad) {
if (this.ad == null) {
this.ad = new ArrayList<>();
}
for (KapuaTad adInList : ad) {
this.ad.add((TadImpl) adInList);
}
public void setAD(List<KapuaTad> ad) {
this.ad = ad;
}

/**
Expand All @@ -108,7 +103,7 @@ public void addAD(KapuaTad ad) {
this.ad = new ArrayList<>();
}

this.ad.add((TadImpl) ad);
this.ad.add(ad);
}

/**
Expand Down Expand Up @@ -136,17 +131,12 @@ public List<KapuaTicon> getIcon() {
if (icon == null) {
icon = new ArrayList<>();
}
return new ArrayList<>(this.icon);
return this.icon;
}

@Override
public void setIcon(List<? extends KapuaTicon> icon) {
if (this.icon == null) {
this.icon = new ArrayList<>();
}
for (KapuaTicon iconInList : icon) {
this.icon.add((TiconImpl) iconInList);
}
public void setIcon(List<KapuaTicon> icon) {
this.icon = icon;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ ILLEGAL_ARGUMENT=An illegal value was provided for the argument {0}. {1}
OPERATION_NOT_ALLOWED=Operation not allowed: {0}
ATTRIBUTE_INVALID=Configuration attribute invalid: {0}
REQUIRED_ATTRIBUTE_MISSING=Required configuration attribute missing: {0}
SERVICE_UNAVAILABLE=The requested service is not available for configuration. {0}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ UNABLE_TO_PARSE_CRON_EXPRESSION=Unable to parse cron expression. {0}
ENTITY_ALREADY_EXIST_IN_ANOTHER_ACCOUNT={0} already exists in another account.
EXTERNAL_ID_ALREADY_EXIST_IN_ANOTHER_ACCOUNT=An entity with the same external id {0} already exists in another account.
SELF_LIMIT_EXCEEDED_IN_CONFIG=Value too small, please remove some items or increase value.
SERVICE_UNAVAILABLE=The requested service is not available for configuration. {0}
ENTITY_NOT_FOUND=The entity of type {0} with id/name {1} was not found.
DEVICE_NOT_FOUND=The selected devices were not found. Please refresh device list.
BUNDLE_START_ERROR=Bundle could not be started. Please check the device log for errors.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public interface KapuaTocd {
@XmlElement(name = "AD", namespace = "http://www.osgi.org/xmlns/metatype/v1.2.0", required = true)
List<KapuaTad> getAD();

void setAD(List<? extends KapuaTad> icon);
void setAD(List<KapuaTad> icon);

/**
* Gets the value of the icon property.
Expand All @@ -90,7 +90,7 @@ public interface KapuaTocd {
@XmlElement(name = "Icon", namespace = "http://www.osgi.org/xmlns/metatype/v1.2.0")
List<KapuaTicon> getIcon();

void setIcon(List<? extends KapuaTicon> icon);
void setIcon(List<KapuaTicon> icon);

/**
* Gets the value of the any property.
Expand Down

0 comments on commit 82b52df

Please sign in to comment.