40 changed files with 414 additions and 277 deletions
@ -0,0 +1,116 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.service.notification.rule.cache; |
|||
|
|||
import com.github.benmanes.caffeine.cache.Cache; |
|||
import com.github.benmanes.caffeine.cache.Caffeine; |
|||
import lombok.Data; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.context.event.EventListener; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.notification.rule.NotificationRule; |
|||
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; |
|||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
|||
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; |
|||
import org.thingsboard.server.dao.notification.NotificationRuleService; |
|||
|
|||
import javax.annotation.PostConstruct; |
|||
import java.util.Arrays; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.concurrent.locks.ReadWriteLock; |
|||
import java.util.concurrent.locks.ReentrantReadWriteLock; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Service |
|||
@RequiredArgsConstructor |
|||
@Slf4j |
|||
public class DefaultNotificationRulesCache implements NotificationRulesCache { |
|||
|
|||
private final NotificationRuleService notificationRuleService; |
|||
|
|||
@Value("${cache.notificationRules.maxSize:1000}") |
|||
private int cacheMaxSize; |
|||
@Value("${cache.notificationRules.timeToLiveInMinutes:30}") |
|||
private int cacheValueTtl; |
|||
private Cache<CacheKey, List<NotificationRule>> cache; |
|||
|
|||
private final ReadWriteLock lock = new ReentrantReadWriteLock(); |
|||
|
|||
@PostConstruct |
|||
private void init() { |
|||
cache = Caffeine.newBuilder() |
|||
.maximumSize(cacheMaxSize) |
|||
.expireAfterAccess(cacheValueTtl, TimeUnit.MINUTES) |
|||
.build(); |
|||
} |
|||
|
|||
@EventListener(ComponentLifecycleMsg.class) |
|||
public void onComponentLifecycleEvent(ComponentLifecycleMsg event) { |
|||
switch (event.getEntityId().getEntityType()) { |
|||
case NOTIFICATION_RULE: |
|||
evict(event.getTenantId()); // TODO: evict by trigger type of the rule
|
|||
break; |
|||
case TENANT: |
|||
if (event.getEvent() == ComponentLifecycleEvent.DELETED) { |
|||
lock.writeLock().lock(); // locking in case rules for tenant are fetched while evicting
|
|||
try { |
|||
evict(event.getTenantId()); |
|||
} finally { |
|||
lock.writeLock().unlock(); |
|||
} |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public List<NotificationRule> get(TenantId tenantId, NotificationRuleTriggerType triggerType) { |
|||
lock.readLock().lock(); |
|||
try { |
|||
log.trace("Retrieving notification rules of type {} for tenant {} from cache", triggerType, tenantId); |
|||
return cache.get(key(tenantId, triggerType), k -> { |
|||
List<NotificationRule> rules = notificationRuleService.findNotificationRulesByTenantIdAndTriggerType(tenantId, triggerType); |
|||
log.trace("Fetched notification rules of type {} for tenant {} (count: {})", triggerType, tenantId, rules.size()); |
|||
return !rules.isEmpty() ? rules : Collections.emptyList(); |
|||
}); |
|||
} finally { |
|||
lock.readLock().unlock(); |
|||
} |
|||
} |
|||
|
|||
private void evict(TenantId tenantId) { |
|||
cache.invalidateAll(Arrays.stream(NotificationRuleTriggerType.values()) |
|||
.map(triggerType -> key(tenantId, triggerType)) |
|||
.collect(Collectors.toList())); |
|||
log.trace("Evicted all notification rules for tenant {} from cache", tenantId); |
|||
} |
|||
|
|||
private static CacheKey key(TenantId tenantId, NotificationRuleTriggerType triggerType) { |
|||
return new CacheKey(tenantId, triggerType); |
|||
} |
|||
|
|||
@Data |
|||
private static class CacheKey { |
|||
private final TenantId tenantId; |
|||
private final NotificationRuleTriggerType triggerType; |
|||
} |
|||
|
|||
} |
|||
@ -1,43 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.notification.cache; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType; |
|||
|
|||
import java.io.Serializable; |
|||
|
|||
@Data |
|||
@AllArgsConstructor |
|||
@NoArgsConstructor |
|||
@Builder |
|||
public class NotificationRuleCacheKey implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 5987113265482170L; |
|||
|
|||
private TenantId tenantId; |
|||
private NotificationRuleTriggerType triggerType; |
|||
|
|||
@Override |
|||
public String toString() { |
|||
return tenantId + "_" + triggerType; |
|||
} |
|||
|
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.notification.cache; |
|||
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|||
import org.springframework.cache.CacheManager; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.cache.CaffeineTbTransactionalCache; |
|||
import org.thingsboard.server.common.data.CacheConstants; |
|||
|
|||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) |
|||
@Service |
|||
public class NotificationRuleCaffeineCache extends CaffeineTbTransactionalCache<NotificationRuleCacheKey, NotificationRuleCacheValue> { |
|||
|
|||
public NotificationRuleCaffeineCache(CacheManager cacheManager) { |
|||
super(cacheManager, CacheConstants.NOTIFICATION_RULES_CACHE); |
|||
} |
|||
|
|||
} |
|||
@ -1,35 +0,0 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
package org.thingsboard.server.dao.notification.cache; |
|||
|
|||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
|||
import org.springframework.data.redis.connection.RedisConnectionFactory; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.cache.CacheSpecsMap; |
|||
import org.thingsboard.server.cache.RedisTbTransactionalCache; |
|||
import org.thingsboard.server.cache.TBRedisCacheConfiguration; |
|||
import org.thingsboard.server.cache.TbFSTRedisSerializer; |
|||
import org.thingsboard.server.common.data.CacheConstants; |
|||
|
|||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") |
|||
@Service |
|||
public class NotificationRuleRedisCache extends RedisTbTransactionalCache<NotificationRuleCacheKey, NotificationRuleCacheValue> { |
|||
|
|||
public NotificationRuleRedisCache(CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory, TBRedisCacheConfiguration configuration) { |
|||
super(CacheConstants.NOTIFICATION_RULES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
#### New platform version notification templatization |
|||
|
|||
<div class="divider"></div> |
|||
<br/> |
|||
|
|||
Notification subject and message fields support templatization. The list of available templatization parameters depends on the template type. |
|||
See the available types and parameters below: |
|||
|
|||
Available template parameters: |
|||
|
|||
* *recipientEmail* - email of the recipient; |
|||
* *recipientFirstName* - first name of the recipient; |
|||
* *recipientLastName* - last name of the recipient; |
|||
* *latestVersion* - the latest platform version available; |
|||
* *latestVersionReleaseNotesUrl* - release notes link for latest version; |
|||
* *upgradeInstructionsUrl* - upgrade instructions link for latest version; |
|||
* *currentVersion* - the current platform version |
|||
* *currentVersionReleaseNotesUrl* - release notes link for current version; |
|||
|
|||
Parameter names must be wrapped using `${...}`. For example: `${recipientFirstName}`. |
|||
You may also modify the value of the parameter with one of the suffixes: |
|||
|
|||
* `upperCase`, for example - `${recipientFirstName:upperCase}` |
|||
* `lowerCase`, for example - `${recipientFirstName:lowerCase}` |
|||
* `capitalize`, for example - `${recipientFirstName:capitalize}` |
|||
|
|||
<div class="divider"></div> |
|||
|
|||
##### Examples |
|||
|
|||
Let's assume that new 3.5.0 version is released but currently deployed version is 3.4.4. The following template: |
|||
|
|||
```text |
|||
New version ${latestVersion} is available. Current version is ${currentVersion} |
|||
{:copy-code} |
|||
``` |
|||
|
|||
will be transformed to: |
|||
|
|||
```text |
|||
New version 3.5.0 is available. Current version is 3.4.4 |
|||
{:copy-code} |
|||
``` |
|||
|
|||
<br> |
|||
<br> |
|||
Loading…
Reference in new issue