use of org.openremote.model.value.Value in project openremote by openremote.
the class MetaEditor method onMetaItemTypeChanged.
protected void onMetaItemTypeChanged(MetaItemEditor itemEditor, boolean updateItem) {
if (updateItem) {
itemEditor.item.clearValue();
// TODO Should use meta item descriptors from server
Value initialValue = Arrays.stream(AssetMeta.values()).filter(assetMeta -> assetMeta.getUrn().equals(itemEditor.nameList.getSelectedValue())).map(MetaItemDescriptor::getInitialValue).findFirst().orElse(null);
ValueType valueType = EnumUtil.enumFromString(ValueType.class, itemEditor.typeList.getSelectedValue()).orElse(null);
if (valueType == ValueType.BOOLEAN && initialValue == null) {
initialValue = Values.create(false);
}
itemEditor.onModified(initialValue);
}
itemEditor.updateValueEditor();
}
use of org.openremote.model.value.Value in project openremote by openremote.
the class MetaEditor method showMetaItemFailure.
protected void showMetaItemFailure(MetaItemEditor metaItemEditor, List<ValidationFailure> failures) {
if (failures != null) {
Optional<MetaItemDescriptor> optionalMetaItemDescriptor = metaItemEditor.getCurrentDescriptor();
String displayName = optionalMetaItemDescriptor.map(metaItemDescriptor -> getMetaItemDisplayName(environment, metaItemDescriptor.name())).orElse(metaItemEditor.getItem().getName().orElse(""));
failures.forEach(failure -> {
if (failure.getReason() == MetaItem.MetaItemFailureReason.META_ITEM_VALUE_IS_REQUIRED) {
// Substitute in value type info
String parameter = EnumUtil.enumFromString(ValueType.class, metaItemEditor.getTypeList().getSelectedValue()).map(Enum::name).orElse("Value");
failure = new ValidationFailure(failure.getReason(), parameter);
}
showValidationError(attribute.getName().orElse(""), displayName, failure);
});
}
}
use of org.openremote.model.value.Value in project openremote by openremote.
the class AssetProcessingService method configure.
@Override
public void configure() throws Exception {
// A client wants to write attribute state through event bus
from(CLIENT_EVENT_TOPIC).routeId("FromClientUpdates").filter(body().isInstanceOf(AttributeEvent.class)).setHeader(HEADER_SOURCE, () -> CLIENT).to(ASSET_QUEUE);
// Process attribute events
/* TODO This message consumer should be transactionally consistent with the database, this is currently not the case
Our "if I have not processed this message before" duplicate detection:
- discard events with source time greater than server processing time (future events)
- discard events with source time less than last applied/stored event source time
- allow the rest (also events with same source time, order of application undefined)
Possible improvements moving towards at-least-once:
- Make AssetUpdateProcessor transactional with a two-phase commit API
- Replace at-most-once ClientEventService with at-least-once capable, embeddable message broker/protocol
- See pseudocode here: http://activemq.apache.org/should-i-use-xa.html
- Do we want JMS/AMQP/WSS or SOME_API/MQTT/WSS? ActiveMQ or Moquette?
*/
from(ASSET_QUEUE).routeId("AssetQueueProcessor").filter(body().isInstanceOf(AttributeEvent.class)).doTry().process(exchange -> withLock(getClass().getSimpleName() + "::processFromAssetQueue", () -> {
AttributeEvent event = exchange.getIn().getBody(AttributeEvent.class);
LOG.finest("Processing: " + event);
if (event.getEntityId() == null || event.getEntityId().isEmpty())
return;
if (event.getAttributeName() == null || event.getAttributeName().isEmpty())
return;
Source source = exchange.getIn().getHeader(HEADER_SOURCE, () -> null, Source.class);
if (source == null) {
throw new AssetProcessingException(MISSING_SOURCE);
}
// Process the asset update in a database transaction, this ensures that processors
// will see consistent database state and we only commit if no processor failed. This
// still won't make this procedure consistent with the message queue from which we consume!
persistenceService.doTransaction(em -> {
ServerAsset asset = assetStorageService.find(em, event.getEntityId(), true);
if (asset == null)
throw new AssetProcessingException(ASSET_NOT_FOUND);
AssetAttribute oldAttribute = asset.getAttribute(event.getAttributeName()).orElse(null);
if (oldAttribute == null)
throw new AssetProcessingException(ATTRIBUTE_NOT_FOUND);
// Agent attributes can't be updated with events
if (asset.getWellKnownType() == AssetType.AGENT) {
throw new AssetProcessingException(ILLEGAL_AGENT_UPDATE);
}
// For executable attributes, non-sensor sources can set a writable attribute execute status
if (oldAttribute.isExecutable() && source != SENSOR) {
Optional<AttributeExecuteStatus> status = event.getValue().flatMap(Values::getString).flatMap(AttributeExecuteStatus::fromString);
if (status.isPresent() && !status.get().isWrite()) {
throw new AssetProcessingException(INVALID_ATTRIBUTE_EXECUTE_STATUS);
}
}
switch(source) {
case CLIENT:
AuthContext authContext = exchange.getIn().getHeader(Constants.AUTH_CONTEXT, AuthContext.class);
if (authContext == null) {
throw new AssetProcessingException(NO_AUTH_CONTEXT);
}
// Check realm, must be accessible
if (!identityService.getIdentityProvider().isTenantActiveAndAccessible(authContext, asset)) {
throw new AssetProcessingException(INSUFFICIENT_ACCESS);
}
// Check read-only
if (oldAttribute.isReadOnly() && !authContext.isSuperUser()) {
throw new AssetProcessingException(INSUFFICIENT_ACCESS);
}
// Regular user must have write assets role
if (!authContext.hasResourceRoleOrIsSuperUser(ClientRole.WRITE_ASSETS.getValue(), Constants.KEYCLOAK_CLIENT_ID)) {
throw new AssetProcessingException(INSUFFICIENT_ACCESS);
}
// Check restricted user
if (identityService.getIdentityProvider().isRestrictedUser(authContext.getUserId())) {
// Must be asset linked to user
if (!assetStorageService.isUserAsset(authContext.getUserId(), event.getEntityId())) {
throw new AssetProcessingException(INSUFFICIENT_ACCESS);
}
// Must be writable by restricted client
if (!oldAttribute.isAccessRestrictedWrite()) {
throw new AssetProcessingException(INSUFFICIENT_ACCESS);
}
}
break;
case SENSOR:
Optional<AssetAttribute> protocolConfiguration = getAgentLink(oldAttribute).flatMap(agentService::getProtocolConfiguration);
// Sensor event must be for an attribute linked to a protocol configuration
if (!protocolConfiguration.isPresent()) {
throw new AssetProcessingException(INVALID_AGENT_LINK);
}
break;
}
// Either use the timestamp of the event or set event time to processing time
long processingTime = timerService.getCurrentTimeMillis();
long eventTime = event.getTimestamp() > 0 ? event.getTimestamp() : processingTime;
// the attribute until after that time (maybe that is desirable behaviour)
if (eventTime - processingTime > 0) {
// TODO: Decide how to handle update events in the future - ignore or change timestamp
throw new AssetProcessingException(EVENT_IN_FUTURE, "current time: " + new Date(processingTime) + "/" + processingTime + ", event time: " + new Date(eventTime) + "/" + eventTime);
}
// Check the last update timestamp of the attribute, ignoring any event that is older than last update
// TODO This means we drop out-of-sequence events but accept events with the same source timestamp
// TODO Several attribute events can occur in the same millisecond, then order of application is undefined
oldAttribute.getValueTimestamp().filter(t -> t >= 0 && eventTime < t).ifPresent(lastStateTime -> {
throw new AssetProcessingException(EVENT_OUTDATED, "last asset state time: " + new Date(lastStateTime) + "/" + lastStateTime + ", event time: " + new Date(eventTime) + "/" + eventTime);
});
// Create a copy of the attribute and set the new value and timestamp
AssetAttribute updatedAttribute = oldAttribute.deepCopy();
updatedAttribute.setValue(event.getValue().orElse(null), eventTime);
// Validate constraints of attribute
List<ValidationFailure> validationFailures = updatedAttribute.getValidationFailures();
if (!validationFailures.isEmpty()) {
throw new AssetProcessingException(ATTRIBUTE_VALIDATION_FAILURE, validationFailures.toString());
}
// Push through all processors
boolean consumedCompletely = processAssetUpdate(em, asset, updatedAttribute, source);
// Publish a new event for clients if no processor consumed the update completely
if (!consumedCompletely) {
publishClientEvent(asset, updatedAttribute);
}
});
})).endDoTry().doCatch(AssetProcessingException.class).process(handleAssetProcessingException(LOG));
}
use of org.openremote.model.value.Value in project openremote by openremote.
the class AssetStorageService method buildAttributeFilter.
protected String buildAttributeFilter(AssetQuery.AttributePredicate attributePredicate, List<ParameterBinder> binders) {
StringBuilder attributeBuilder = new StringBuilder();
if (attributePredicate.name != null) {
attributeBuilder.append(attributePredicate.name.caseSensitive ? " and AX.key" : " and upper(AX.key)");
switch(attributePredicate.name.match) {
case EXACT:
attributeBuilder.append(" = ? ");
break;
case NOT_EXACT:
attributeBuilder.append(" <> ? ");
break;
case BEGIN:
case END:
case CONTAINS:
attributeBuilder.append(" like ? ");
break;
}
final int pos = binders.size() + 1;
binders.add(st -> st.setString(pos, attributePredicate.name.prepareValue()));
}
if (attributePredicate.value != null) {
if (attributePredicate.value instanceof AssetQuery.StringPredicate) {
AssetQuery.StringPredicate stringPredicate = (AssetQuery.StringPredicate) attributePredicate.value;
attributeBuilder.append(stringPredicate.caseSensitive ? " and AX.VALUE #>> '{value}'" : " and upper(AX.VALUE #>> '{value}')");
switch(stringPredicate.match) {
case EXACT:
attributeBuilder.append(" = ? ");
break;
case NOT_EXACT:
attributeBuilder.append(" <> ? ");
break;
case BEGIN:
case END:
case CONTAINS:
attributeBuilder.append(" like ? ");
break;
}
final int pos = binders.size() + 1;
binders.add(st -> st.setString(pos, stringPredicate.prepareValue()));
} else if (attributePredicate.value instanceof AssetQuery.BooleanPredicate) {
AssetQuery.BooleanPredicate booleanPredicate = (AssetQuery.BooleanPredicate) attributePredicate.value;
attributeBuilder.append(" and AX.VALUE #> '{value}' = to_jsonb(").append(booleanPredicate.value).append(")");
} else if (attributePredicate.value instanceof AssetQuery.StringArrayPredicate) {
AssetQuery.StringArrayPredicate stringArrayPredicate = (AssetQuery.StringArrayPredicate) attributePredicate.value;
for (int i = 0; i < stringArrayPredicate.predicates.length; i++) {
AssetQuery.StringPredicate stringPredicate = stringArrayPredicate.predicates[i];
attributeBuilder.append(stringPredicate.caseSensitive ? " and AX.VALUE #> '{value}' ->> " + i : " and upper(AX.VALUE #> '{value}' ->> " + i + ")");
switch(stringPredicate.match) {
case EXACT:
attributeBuilder.append(" = ? ");
break;
case NOT_EXACT:
attributeBuilder.append(" <> ? ");
break;
case BEGIN:
case END:
case CONTAINS:
attributeBuilder.append(" like ? ");
break;
}
final int pos = binders.size() + 1;
binders.add(st -> st.setString(pos, stringPredicate.prepareValue()));
}
} else if (attributePredicate.value instanceof AssetQuery.DateTimePredicate) {
AssetQuery.DateTimePredicate dateTimePredicate = (AssetQuery.DateTimePredicate) attributePredicate.value;
attributeBuilder.append(" and to_timestamp(AX.VALUE #>> '{value}', ?)");
final int keyFormatPos = binders.size() + 1;
binders.add(st -> st.setString(keyFormatPos, dateTimePredicate.dateFormat));
final int pos = binders.size() + 1;
binders.add(st -> st.setString(pos, dateTimePredicate.value));
final int formatPos = binders.size() + 1;
binders.add(st -> st.setString(formatPos, dateTimePredicate.dateFormat));
switch(dateTimePredicate.operator) {
case EQUALS:
attributeBuilder.append(" = to_timestamp(?, ?)");
break;
case NOT_EQUALS:
attributeBuilder.append(" <> to_timestamp(?, ?)");
break;
case GREATER_THAN:
attributeBuilder.append(" > to_timestamp(?, ?)");
break;
case GREATER_EQUALS:
attributeBuilder.append(" >= to_timestamp(?, ?)");
break;
case LESS_THAN:
attributeBuilder.append(" < to_timestamp(?, ?)");
break;
case LESS_EQUALS:
attributeBuilder.append(" <= to_timestamp(?, ?)");
break;
case BETWEEN:
attributeBuilder.append(" BETWEEN to_timestamp(?, ?) AND to_timestamp(?, ?)");
final int pos2 = binders.size() + 1;
binders.add(st -> st.setString(pos2, dateTimePredicate.rangeValue));
final int formatPos2 = binders.size() + 1;
binders.add(st -> st.setString(formatPos2, dateTimePredicate.dateFormat));
break;
}
} else if (attributePredicate.value instanceof AssetQuery.NumberPredicate) {
AssetQuery.NumberPredicate numberPredicate = (AssetQuery.NumberPredicate) attributePredicate.value;
attributeBuilder.append(" and (AX.VALUE #>> '{value}')::numeric");
switch(numberPredicate.operator) {
case EQUALS:
default:
attributeBuilder.append(" = ?");
break;
case NOT_EQUALS:
attributeBuilder.append(" <> ?");
break;
case GREATER_THAN:
attributeBuilder.append(" > ?");
break;
case GREATER_EQUALS:
attributeBuilder.append(" >= ?");
break;
case LESS_THAN:
attributeBuilder.append(" < ?");
break;
case LESS_EQUALS:
attributeBuilder.append(" <= ?");
break;
case BETWEEN:
attributeBuilder.append(" BETWEEN ? AND ?");
break;
}
final int pos = binders.size() + 1;
switch(numberPredicate.numberType) {
case DOUBLE:
default:
binders.add(st -> st.setDouble(pos, numberPredicate.value));
if (numberPredicate.operator == Operator.BETWEEN) {
final int pos2 = binders.size() + 1;
binders.add(st -> st.setDouble(pos, numberPredicate.rangeValue));
}
break;
case INTEGER:
binders.add(st -> st.setInt(pos, (int) numberPredicate.value));
if (numberPredicate.operator == Operator.BETWEEN) {
final int pos2 = binders.size() + 1;
binders.add(st -> st.setInt(pos2, (int) numberPredicate.rangeValue));
}
break;
}
}
}
return attributeBuilder.toString();
}
use of org.openremote.model.value.Value in project openremote by openremote.
the class Simulator method writeView.
protected void writeView() {
clear();
addLabel(environment.getMessages().simulator());
formGroups.clear();
List<SimulatorElement> sortedElements = Arrays.asList(simulatorState.getElements());
sortedElements.sort(Comparator.comparing(o -> simulatorState.getElementName(o)));
for (SimulatorElement element : sortedElements) {
FormGroup formGroup = new FormGroup();
String elementName = simulatorState.getElementName(element);
FormLabel formLabel = new FormLabel(elementName);
formLabel.addStyleName("largest");
formGroup.setFormLabel(formLabel);
FormField formField = new FormField();
formGroup.setFormField(formField);
// Don't push simulator value validation up to the presenter as it is a special case that should
// just be evaluated in-situ and shouldn't invalidate the parent attribute
Consumer<Value> onModified = value -> {
element.setValue(value);
List<ValidationFailure> failures = element.getValidationFailures();
formGroup.setError(failures != null && !failures.isEmpty());
};
ValueType valueType = element.getExpectedType().getValueType();
IsWidget editor = valueEditorSupplier.createValueEditor(element, valueType, style, parentView, onModified);
formField.add(editor);
formGroups.put(element.getAttributeRef(), formGroup);
add(formGroup);
}
if (sortedElements.size() > 0) {
FormGroup submitGroup = new FormGroup();
submitGroup.getElement().getStyle().setWidth(80, com.google.gwt.dom.client.Style.Unit.PCT);
FormField submitField = new FormField();
submitGroup.setFormField(submitField);
FormButton writeButton = new FormButton(environment.getMessages().writeSimulatorState());
writeButton.setPrimary(true);
writeButton.addClickHandler(event -> {
if (isValid()) {
environment.getEventService().dispatch(simulatorState);
environment.getEventBus().dispatch(new ShowSuccessEvent(environment.getMessages().simulatorStateSubmitted()));
}
});
submitField.add(writeButton);
add(submitGroup);
} else {
add(new FormInlineLabel(environment.getMessages().noAttributesLinkedToSimulator()));
}
// "Blink" the editor so users know there might be a new value
for (FormGroup formGroup : formGroups.values()) {
formGroup.addStyleName(environment.getWidgetStyle().HighlightBackground());
}
Browser.getWindow().setTimeout(() -> {
for (FormGroup formGroup : formGroups.values()) formGroup.removeStyleName(environment.getWidgetStyle().HighlightBackground());
}, 250);
}
Aggregations