use of org.openremote.model.notification.PushNotificationMessage in project openremote by openremote.
the class ORConsoleGeofenceAssetAdapter method notifyAssetGeofencesChanged.
/**
* Send a silent push notification to the console to get it to refresh its geofences
*/
protected void notifyAssetGeofencesChanged(Set<String> assetIds) {
if (assetIds == null) {
return;
}
List<String> ids = new ArrayList<>(assetIds);
ObjectNode data = ValueUtil.JSON.createObjectNode();
data.put("action", "GEOFENCE_REFRESH");
// Break into batches of 10 sent every 10s to avoid consoles bombarding the backend
int rows = (int) Math.ceil((((float) ids.size()) / 10));
IntStream.range(0, rows).forEach(i -> {
final List<Notification.Target> subTargets = ids.subList(10 * i, Math.min(10 + (10 * i), ids.size())).stream().map(id -> new Notification.Target(Notification.TargetType.ASSET, id)).collect(Collectors.toList());
final Notification notification = new Notification("GeofenceRefresh", new PushNotificationMessage().setData(data), null, null, null);
notification.setTargets(subTargets);
executorService.schedule(() -> {
LOG.info("Notifiying consoles that geofences have changed: " + notification.getTargets());
notificationService.sendNotification(notification);
}, (long) i * NOTIFY_ASSETS_BATCH_MILLIS, TimeUnit.MILLISECONDS);
});
}
use of org.openremote.model.notification.PushNotificationMessage in project openremote by openremote.
the class PushNotificationHandler method getTargets.
@Override
public List<Notification.Target> getTargets(Notification.Source source, String sourceId, List<Notification.Target> targets, AbstractNotificationMessage message) {
// Check if message is going to a topic if so then filter consoles subscribed to that topic
PushNotificationMessage pushMessage = (PushNotificationMessage) message;
List<Notification.Target> mappedTargets = new ArrayList<>();
if (pushMessage.getTargetType() == TOPIC || pushMessage.getTargetType() == CONDITION) {
mappedTargets.add(new Notification.Target(Notification.TargetType.CUSTOM, pushMessage.getTargetType() + ": " + pushMessage.getTarget()));
return mappedTargets;
}
if (targets != null) {
targets.forEach(target -> {
Notification.TargetType targetType = target.getType();
String targetId = target.getId();
switch(targetType) {
case TENANT:
// Get all console assets with a push provider defined within the specified tenant
List<Asset<?>> consoleAssets = assetStorageService.findAll(new AssetQuery().select(new AssetQuery.Select().excludeAttributes()).tenant(new TenantPredicate(targetId)).types(ConsoleAsset.class).attributes(new AttributePredicate(ConsoleAsset.CONSOLE_PROVIDERS, null, false, new NameValuePredicate.Path(PushNotificationMessage.TYPE))));
// Get all user ids which have pushNotificationsDisabled set to false
String[] userIds = Arrays.stream(managerIdentityService.getIdentityProvider().queryUsers(new UserQuery().tenant(new TenantPredicate((targetId))))).filter(user -> Boolean.parseBoolean(user.getAttributes().getOrDefault(KEYCLOAK_USER_ATTRIBUTE_PUSH_NOTIFICATIONS_DISABLED, Collections.singletonList("false")).get(0))).map(User::getId).toArray(String[]::new);
String[] assetIds = assetStorageService.findUserAssetLinks(targetId, null, null).stream().filter(userAssetLink -> Arrays.stream(userIds).anyMatch(userId -> userId.equals(userAssetLink.getId().getUserId()))).map(userAssetLink -> userAssetLink.getId().getAssetId()).toArray(String[]::new);
// Remove consoleAssets which are linked to an User which has pushNotificationsDisabled set to false
consoleAssets = consoleAssets.stream().filter(consoleAsset -> Arrays.stream(assetIds).noneMatch(assetId -> assetId.equals(consoleAsset.getId()))).collect(Collectors.toList());
mappedTargets.addAll(consoleAssets.stream().map(asset -> new Notification.Target(Notification.TargetType.ASSET, asset.getId())).collect(Collectors.toList()));
break;
case USER:
Optional<User> user = Arrays.stream(managerIdentityService.getIdentityProvider().queryUsers(new UserQuery().ids(targetId))).findFirst();
if (user.isPresent() && !Boolean.parseBoolean(user.get().getAttributes().getOrDefault(KEYCLOAK_USER_ATTRIBUTE_PUSH_NOTIFICATIONS_DISABLED, Collections.singletonList("false")).get(0))) {
// Get all console assets linked to the specified user
String[] ids = assetStorageService.findUserAssetLinks(null, targetId, null).stream().map(userAssetLink -> userAssetLink.getId().getAssetId()).toArray(String[]::new);
if (ids.length > 0) {
mappedTargets.addAll(assetStorageService.findAll(new AssetQuery().select(new AssetQuery.Select().excludeAttributes()).ids(ids).types(ConsoleAsset.class).attributes(new AttributePredicate(ConsoleAsset.CONSOLE_PROVIDERS, null, false, new NameValuePredicate.Path(PushNotificationMessage.TYPE)))).stream().map(asset -> new Notification.Target(Notification.TargetType.ASSET, asset.getId())).collect(Collectors.toList()));
}
} else {
LOG.fine("No console assets linked to target user");
return;
}
break;
case ASSET:
// Find all console descendants of the specified asset
consoleAssets = assetStorageService.findAll(new AssetQuery().select(new AssetQuery.Select().excludeAttributes()).paths(new PathPredicate(targetId)).types(ConsoleAsset.class).attributes(new AttributePredicate(ConsoleAsset.CONSOLE_PROVIDERS, null, false, new NameValuePredicate.Path(PushNotificationMessage.TYPE))));
UserAssetLink[] userAssetLinks = consoleAssets.stream().map(consoleAsset -> assetStorageService.findUserAssetLinks(null, null, consoleAsset.getId())).flatMap(Collection::stream).toArray(UserAssetLink[]::new);
// Get all user ids which have pushNotificationsDisabled set to false
assetIds = Arrays.stream(userAssetLinks).filter(userAssetLink -> Arrays.stream(managerIdentityService.getIdentityProvider().queryUsers(new UserQuery().asset(new UserAssetPredicate(userAssetLink.getId().getAssetId())))).filter(user1 -> Boolean.parseBoolean(user1.getAttributes().getOrDefault(KEYCLOAK_USER_ATTRIBUTE_PUSH_NOTIFICATIONS_DISABLED, Collections.singletonList("false")).get(0))).map(User::getId).anyMatch(userId -> userId.equals(userAssetLink.getId().getUserId()))).map(userAssetLink -> userAssetLink.getId().getAssetId()).toArray(String[]::new);
// Remove consoleAssets which are linked to an User which has pushNotificationsDisabled set to false
consoleAssets = consoleAssets.stream().filter(consoleAsset -> Arrays.stream(assetIds).noneMatch(assetId -> assetId.equals(consoleAsset.getId()))).collect(Collectors.toList());
mappedTargets.addAll(consoleAssets.stream().map(asset -> new Notification.Target(Notification.TargetType.ASSET, asset.getId())).collect(Collectors.toList()));
break;
}
});
}
return mappedTargets;
}
use of org.openremote.model.notification.PushNotificationMessage in project openremote by openremote.
the class JsonRulesBuilder method buildRuleActionExecution.
protected static RuleActionExecution buildRuleActionExecution(JsonRule rule, RuleAction ruleAction, String actionsName, int index, boolean useUnmatched, RulesFacts facts, RuleState ruleState, Assets assetsFacade, Users usersFacade, Notifications notificationsFacade, PredictedDatapoints predictedDatapointsFacade) {
if (ruleAction instanceof RuleActionNotification) {
RuleActionNotification notificationAction = (RuleActionNotification) ruleAction;
if (notificationAction.notification != null) {
Notification notification = notificationAction.notification;
if (notification.getMessage() != null) {
String body = null;
boolean isEmail = Objects.equals(notification.getMessage().getType(), EmailNotificationMessage.TYPE);
boolean isPush = Objects.equals(notification.getMessage().getType(), PushNotificationMessage.TYPE);
boolean isHtml = false;
if (isEmail) {
EmailNotificationMessage email = (EmailNotificationMessage) notification.getMessage();
isHtml = !TextUtil.isNullOrEmpty(email.getHtml());
body = isHtml ? email.getHtml() : email.getText();
} else if (isPush) {
PushNotificationMessage pushNotificationMessage = (PushNotificationMessage) notification.getMessage();
body = pushNotificationMessage.getBody();
}
if (!TextUtil.isNullOrEmpty(body)) {
if (body.contains(PLACEHOLDER_TRIGGER_ASSETS)) {
// Need to clone the notification
notification = ValueUtil.clone(notification);
String triggeredAssetInfo = buildTriggeredAssetInfo(useUnmatched, ruleState, isHtml);
body = body.replace(PLACEHOLDER_TRIGGER_ASSETS, triggeredAssetInfo);
if (isEmail) {
EmailNotificationMessage email = (EmailNotificationMessage) notification.getMessage();
if (isHtml) {
email.setHtml(body);
} else {
email.setText(body);
}
} else if (isPush) {
PushNotificationMessage pushNotificationMessage = (PushNotificationMessage) notification.getMessage();
pushNotificationMessage.setBody(body);
}
}
}
}
// Transfer the rule action target into notification targets
Notification.TargetType targetType = Notification.TargetType.ASSET;
if (ruleAction.target != null) {
if (ruleAction.target.users != null && ruleAction.target.conditionAssets == null && ruleAction.target.assets == null && ruleAction.target.matchedAssets == null) {
targetType = Notification.TargetType.USER;
} else if (ruleAction.target.custom != null && ruleAction.target.conditionAssets == null && ruleAction.target.assets == null && ruleAction.target.matchedAssets == null) {
targetType = Notification.TargetType.CUSTOM;
}
}
Collection<String> ids = getRuleActionTargetIds(ruleAction.target, useUnmatched, ruleState, assetsFacade, usersFacade, facts);
if (ids == null) {
notification.setTargets((List<Notification.Target>) null);
} else {
Notification.TargetType finalTargetType = targetType;
notification.setTargets(ids.stream().map(id -> new Notification.Target(finalTargetType, id)).collect(Collectors.toList()));
}
log(Level.FINE, "Sending notification for rule action: " + rule.name + " '" + actionsName + "' action index " + index);
Notification finalNotification = notification;
return new RuleActionExecution(() -> notificationsFacade.send(finalNotification), 0);
}
}
if (ruleAction instanceof RuleActionWriteAttribute) {
if (targetIsNotAssets(ruleAction.target)) {
return null;
}
RuleActionWriteAttribute attributeAction = (RuleActionWriteAttribute) ruleAction;
if (TextUtil.isNullOrEmpty(attributeAction.attributeName)) {
log(Level.WARNING, "Attribute name is missing for rule action: " + rule.name + " '" + actionsName + "' action index " + index);
return null;
}
Collection<String> ids = getRuleActionTargetIds(ruleAction.target, useUnmatched, ruleState, assetsFacade, usersFacade, facts);
if (ids == null || ids.isEmpty()) {
log(Level.INFO, "No targets for write attribute rule action so skipping: " + rule.name + " '" + actionsName + "' action index " + index);
return null;
}
log(Level.FINE, "Writing attribute '" + attributeAction.attributeName + "' for " + ids.size() + " asset(s) for rule action: " + rule.name + " '" + actionsName + "' action index " + index);
return new RuleActionExecution(() -> ids.forEach(id -> assetsFacade.dispatch(id, attributeAction.attributeName, attributeAction.value)), 0);
}
if (ruleAction instanceof RuleActionWait) {
long millis = ((RuleActionWait) ruleAction).millis;
if (millis > 0) {
return new RuleActionExecution(null, millis);
}
log(Level.FINEST, "Invalid delay for wait rule action so skipping: " + rule.name + " '" + actionsName + "' action index " + index);
}
if (ruleAction instanceof RuleActionUpdateAttribute) {
if (targetIsNotAssets(ruleAction.target)) {
log(Level.FINEST, "Invalid target update attribute rule action so skipping: " + rule.name + " '" + actionsName + "' action index " + index);
return null;
}
RuleActionUpdateAttribute attributeUpdateAction = (RuleActionUpdateAttribute) ruleAction;
if (TextUtil.isNullOrEmpty(attributeUpdateAction.attributeName)) {
log(Level.WARNING, "Attribute name is missing for rule action: " + rule.name + " '" + actionsName + "' action index " + index);
return null;
}
List<String> matchingAssetIds;
if (ruleAction.target == null || ruleAction.target.assets == null) {
if (ruleAction.target != null && ruleAction.target.users != null) {
throw new IllegalStateException("Cannot use action type '" + RuleActionUpdateAttribute.class.getSimpleName() + "' with user target");
}
matchingAssetIds = new ArrayList<>(getRuleActionTargetIds(ruleAction.target, useUnmatched, ruleState, assetsFacade, usersFacade, facts));
} else {
matchingAssetIds = facts.matchAssetState(ruleAction.target.assets).map(AssetState::getId).distinct().collect(Collectors.toList());
}
if (matchingAssetIds.isEmpty()) {
log(Level.INFO, "No targets for update attribute rule action so skipping: " + rule.name + " '" + actionsName + "' action index " + index);
return null;
}
// Look for the current value within the asset state facts (asset/attribute has to be in scope of this rule engine and have a rule state meta item)
List<AssetState<?>> matchingAssetStates = matchingAssetIds.stream().map(assetId -> facts.getAssetStates().stream().filter(state -> state.getId().equals(assetId) && state.getName().equals(attributeUpdateAction.attributeName)).findFirst().orElseGet(() -> {
log(Level.WARNING, "Failed to find attribute in rule states for attribute update: " + new AttributeRef(assetId, attributeUpdateAction.attributeName));
return null;
})).filter(Objects::nonNull).collect(Collectors.toList());
if (matchingAssetStates.isEmpty()) {
log(Level.WARNING, "No asset states matched to apply update attribute action to");
return null;
}
return new RuleActionExecution(() -> matchingAssetStates.forEach(assetState -> {
Object value = assetState.getValue().orElse(null);
Class<?> valueType = assetState.getType().getType();
boolean isArray = ValueUtil.isArray(valueType);
if (!isArray && !ValueUtil.isObject(valueType)) {
log(Level.WARNING, "Rule action target asset cannot determine value type or incompatible value type for attribute: " + assetState);
} else {
// Convert value to JSON Node to easily manipulate it
value = isArray ? ValueUtil.convert(value, ArrayNode.class) : ValueUtil.convert(value, ObjectNode.class);
switch(attributeUpdateAction.updateAction) {
case ADD:
if (isArray) {
value = value == null ? ValueUtil.JSON.createArrayNode() : value;
((ArrayNode) value).add(ValueUtil.convert(attributeUpdateAction.value, JsonNode.class));
} else {
value = value == null ? ValueUtil.JSON.createObjectNode() : value;
((ObjectNode) value).set(attributeUpdateAction.key, ValueUtil.convert(attributeUpdateAction.value, JsonNode.class));
}
break;
case ADD_OR_REPLACE:
case REPLACE:
if (isArray) {
value = value == null ? ValueUtil.JSON.createArrayNode() : value;
ArrayNode arrayValue = (ArrayNode) value;
if (attributeUpdateAction.index != null && arrayValue.size() >= attributeUpdateAction.index) {
arrayValue.set(attributeUpdateAction.index, ValueUtil.convert(attributeUpdateAction.value, JsonNode.class));
} else {
arrayValue.add(ValueUtil.convert(attributeUpdateAction.value, JsonNode.class));
}
} else {
value = value == null ? ValueUtil.JSON.createObjectNode() : value;
if (!TextUtil.isNullOrEmpty(attributeUpdateAction.key)) {
((ObjectNode) value).set(attributeUpdateAction.key, ValueUtil.convert(attributeUpdateAction.value, JsonNode.class));
} else {
log(Level.WARNING, "JSON Rule: Rule action missing required 'key': " + ValueUtil.asJSON(attributeUpdateAction));
}
}
break;
case DELETE:
if (value != null) {
if (isArray) {
((ArrayNode) value).remove(attributeUpdateAction.index);
} else {
((ObjectNode) value).remove(attributeUpdateAction.key);
}
}
break;
case CLEAR:
if (isArray) {
value = ValueUtil.JSON.createArrayNode();
} else {
value = ValueUtil.JSON.createObjectNode();
}
break;
}
log(Level.FINE, "Updating attribute for rule action: " + rule.name + " '" + actionsName + "' action index " + index + ": " + assetState);
assetsFacade.dispatch(assetState.getId(), attributeUpdateAction.attributeName, value);
}
}), 0);
}
log(Level.FINE, "Unsupported rule action: " + rule.name + " '" + actionsName + "' action index " + index);
return null;
}
use of org.openremote.model.notification.PushNotificationMessage in project openremote by openremote.
the class PushNotificationHandler method sendMessage.
@Override
public NotificationSendResult sendMessage(long id, Notification.Source source, String sourceId, Notification.Target target, AbstractNotificationMessage message) {
Notification.TargetType targetType = target.getType();
String targetId = target.getId();
if (targetType != Notification.TargetType.ASSET && targetType != Notification.TargetType.CUSTOM) {
LOG.warning("Target type not supported: " + targetType);
return NotificationSendResult.failure("Target type not supported: " + targetType);
}
if (!isValid()) {
LOG.warning("FCM invalid configuration so ignoring");
return NotificationSendResult.failure("FCM invalid configuration so ignoring");
}
// Check this asset has an FCM token (i.e. it is registered for push notifications)
String fcmToken = consoleFCMTokenMap.get(targetId);
if (TextUtil.isNullOrEmpty(fcmToken)) {
LOG.warning("No FCM token found for console: " + targetId);
return NotificationSendResult.failure("No FCM token found for console: " + targetId);
}
PushNotificationMessage pushMessage = (PushNotificationMessage) message;
// Assume DEVICE target if not specified
if (pushMessage.getTargetType() == null) {
pushMessage.setTargetType(DEVICE);
}
switch(pushMessage.getTargetType()) {
case DEVICE:
// Always use fcm token from the console asset (so users cannot target other devices)
pushMessage.setTarget(fcmToken);
break;
case TOPIC:
// TODO: Decide how to handle FCM topic support (too much power for users to put anything in target)
break;
case CONDITION:
// TODO: Decide how to handle conditions support (too much power for users to put anything in target)
break;
}
return sendMessage(buildFCMMessage(id, pushMessage));
}
Aggregations