use of org.openremote.model.notification.Notification in project openremote by openremote.
the class NotificationService method configure.
@Override
public void configure() throws Exception {
from(NOTIFICATION_QUEUE).routeId("NotificationQueueProcessor").doTry().process(exchange -> {
Notification notification = exchange.getIn().getBody(Notification.class);
if (notification == null) {
throw new NotificationProcessingException(MISSING_NOTIFICATION, "Notification must be set");
}
LOG.finest("Processing: " + notification.getName());
if (notification.getMessage() == null) {
throw new NotificationProcessingException(MISSING_MESSAGE, "Notification message must be set");
}
Notification.Source source = exchange.getIn().getHeader(HEADER_SOURCE, () -> null, Notification.Source.class);
if (source == null) {
throw new NotificationProcessingException(MISSING_SOURCE);
}
// Validate handler and message
NotificationHandler handler = notificationHandlerMap.get(notification.getMessage().getType());
if (handler == null) {
throw new NotificationProcessingException(UNSUPPORTED_MESSAGE_TYPE, "No handler for message type: " + notification.getMessage().getType());
}
if (!handler.isValid()) {
throw new NotificationProcessingException(NOTIFICATION_HANDLER_CONFIG_ERROR, "Handler is not valid: " + handler.getTypeName());
}
if (!handler.isMessageValid(notification.getMessage())) {
throw new NotificationProcessingException(INVALID_MESSAGE);
}
// Validate access and map targets to handler compatible targets
String realm = null;
String userId = null;
String assetId = null;
AtomicReference<String> sourceId = new AtomicReference<>("");
boolean isSuperUser = false;
boolean isRestrictedUser = false;
switch(source) {
case INTERNAL:
isSuperUser = true;
break;
case CLIENT:
AuthContext authContext = exchange.getIn().getHeader(Constants.AUTH_CONTEXT, AuthContext.class);
if (authContext == null) {
// Anonymous clients cannot send notifications
throw new NotificationProcessingException(INSUFFICIENT_ACCESS);
}
realm = authContext.getAuthenticatedRealm();
userId = authContext.getUserId();
sourceId.set(userId);
isSuperUser = authContext.isSuperUser();
isRestrictedUser = identityService.getIdentityProvider().isRestrictedUser(authContext);
break;
case GLOBAL_RULESET:
isSuperUser = true;
break;
case TENANT_RULESET:
realm = exchange.getIn().getHeader(Notification.HEADER_SOURCE_ID, String.class);
sourceId.set(realm);
break;
case ASSET_RULESET:
assetId = exchange.getIn().getHeader(Notification.HEADER_SOURCE_ID, String.class);
sourceId.set(assetId);
Asset<?> asset = assetStorageService.find(assetId, false);
realm = asset.getRealm();
break;
}
LOG.info("Sending " + notification.getMessage().getType() + " notification '" + notification.getName() + "': '" + source + ":" + sourceId.get() + "' -> " + notification.getTargets());
// Check access permissions
checkAccess(source, sourceId.get(), notification.getTargets(), realm, userId, isSuperUser, isRestrictedUser, assetId);
// Get the list of notification targets
List<Notification.Target> mappedTargetsList = handler.getTargets(source, sourceId.get(), notification.getTargets(), notification.getMessage());
if (mappedTargetsList == null || mappedTargetsList.isEmpty()) {
throw new NotificationProcessingException(MISSING_TARGETS, "Notification targets must be set");
}
// Filter targets based on repeat frequency
if (!TextUtil.isNullOrEmpty(notification.getName()) && (!TextUtil.isNullOrEmpty(notification.getRepeatInterval()) || notification.getRepeatFrequency() != null)) {
mappedTargetsList = mappedTargetsList.stream().filter(target -> okToSendNotification(source, sourceId.get(), target, notification)).collect(Collectors.toList());
}
// Send message to each applicable target
AtomicBoolean success = new AtomicBoolean(true);
mappedTargetsList.forEach(target -> {
boolean targetSuccess = persistenceService.doReturningTransaction(em -> {
// commit the notification first to get the ID
SentNotification sentNotification = new SentNotification().setName(notification.getName()).setType(notification.getMessage().getType()).setSource(source).setSourceId(sourceId.get()).setTarget(target.getType()).setTargetId(target.getId()).setMessage(notification.getMessage()).setSentOn(Date.from(timerService.getNow()));
sentNotification = em.merge(sentNotification);
long id = sentNotification.getId();
try {
NotificationSendResult result = handler.sendMessage(id, source, sourceId.get(), target, notification.getMessage());
if (result.isSuccess()) {
LOG.info("Notification sent '" + id + "': " + target);
} else {
LOG.warning("Notification failed '" + id + "': " + target + ", reason=" + result.getMessage());
sentNotification.setError(TextUtil.isNullOrEmpty(result.getMessage()) ? "Unknown error" : result.getMessage());
}
// Merge the sent notification again with the message included just in case the handler modified the message
sentNotification.setMessage(notification.getMessage());
em.merge(sentNotification);
} catch (Exception e) {
LOG.log(Level.SEVERE, "Notification handler threw an exception whilst sending notification '" + id + "'", e);
sentNotification.setError(TextUtil.isNullOrEmpty(e.getMessage()) ? "Unknown error" : e.getMessage());
em.merge(sentNotification);
}
return sentNotification.getError() == null;
});
if (!targetSuccess) {
success.set(false);
}
});
exchange.getOut().setBody(success.get());
}).endDoTry().doCatch(NotificationProcessingException.class).process(handleNotificationProcessingException(LOG));
}
use of org.openremote.model.notification.Notification 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.Notification 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);
});
}
Aggregations