Search in sources :

Example 1 with AttributeEvent

use of org.openremote.model.attribute.AttributeEvent in project openremote by openremote.

the class RulesFacts method invalidateAssetStateAndDispatch.

protected RulesFacts invalidateAssetStateAndDispatch(String assetId, String attributeName, Value value) {
    // Remove the asset state from the facts, it is invalid now
    getAssetStates().removeIf(assetState -> {
        boolean invalid = assetState.getId().equals(assetId) && assetState.getAttributeName().equals(attributeName);
        if (invalid) {
            if (LOG.isLoggable(Level.FINEST)) {
                LOG.finest("Fact change (INTERNAL DELETE): " + assetState + " - on: " + loggingContext);
            }
            // Maintain index of all asset states for this asset by ID
            Collection<AssetState> assetIdIndexCollection = assetIdIndex.get(assetState.getId());
            if (assetIdIndexCollection != null) {
                assetIdIndexCollection.remove(assetState);
            }
            // Maintain index of all asset states for this asset by type
            Collection<AssetState> assetTypeIndexCollection = assetTypeIndex.get(assetState.getTypeString());
            if (assetTypeIndexCollection != null) {
                assetTypeIndexCollection.remove(assetState);
            }
        }
        return invalid;
    });
    // Dispatch the update to the asset processing service
    AttributeEvent attributeEvent = new AttributeEvent(assetId, attributeName, value);
    LOG.finest("Dispatching " + attributeEvent + " - on: " + loggingContext);
    assetsFacade.dispatch(attributeEvent);
    return this;
}
Also used : AssetState(org.openremote.model.rules.AssetState) AttributeEvent(org.openremote.model.attribute.AttributeEvent)

Example 2 with AttributeEvent

use of org.openremote.model.attribute.AttributeEvent in project openremote by openremote.

the class AssetViewActivity method onAttributeEvent.

protected void onAttributeEvent(AttributeEvent attributeEvent) {
    for (AttributeView attributeView : attributeViews) {
        AssetAttribute assetAttribute = attributeView.getAttribute();
        Optional<AttributeRef> assetAttributeRef = assetAttribute.getReference();
        if (assetAttributeRef.map(ref -> ref.equals(attributeEvent.getAttributeRef())).orElse(false)) {
            assetAttribute.setValue(attributeEvent.getValue().orElse(null), attributeEvent.getTimestamp());
            attributeView.onAttributeChanged(attributeEvent.getTimestamp());
            break;
        }
    }
}
Also used : SimulatorState(org.openremote.model.simulator.SimulatorState) DatapointInterval(org.openremote.model.datapoint.DatapointInterval) AssetMapper(org.openremote.app.client.assets.AssetMapper) Environment(org.openremote.app.client.Environment) ProtocolConfiguration(org.openremote.model.asset.agent.ProtocolConfiguration) AttributeRef(org.openremote.model.attribute.AttributeRef) AssetDatapointResource(org.openremote.model.datapoint.AssetDatapointResource) AttributeViewImpl(org.openremote.app.client.assets.attributes.AttributeViewImpl) ObjectValueMapper(org.openremote.app.client.interop.value.ObjectValueMapper) FormButton(org.openremote.app.client.widget.FormButton) NumberDatapoint(org.openremote.model.datapoint.NumberDatapoint) ArrayList(java.util.ArrayList) AgentLink(org.openremote.model.asset.agent.AgentLink) Inject(javax.inject.Inject) ReadAssetAttributesEvent(org.openremote.model.asset.ReadAssetAttributesEvent) TenantFilter(org.openremote.model.event.shared.TenantFilter) AttributeEvent(org.openremote.model.attribute.AttributeEvent) URL(com.google.gwt.http.client.URL) AbstractAttributeViewExtension(org.openremote.app.client.assets.attributes.AbstractAttributeViewExtension) AttributeView(org.openremote.app.client.assets.attributes.AttributeView) AgentStatusEventMapper(org.openremote.app.client.assets.AgentStatusEventMapper) Consumer(org.openremote.model.interop.Consumer) AgentResource(org.openremote.model.asset.agent.AgentResource) MapResource(org.openremote.model.map.MapResource) AgentStatusEvent(org.openremote.model.asset.agent.AgentStatusEvent) AssetType(org.openremote.model.asset.AssetType) DatapointBrowser(org.openremote.app.client.datapoint.DatapointBrowser) JsonEditor(org.openremote.app.client.app.dialog.JsonEditor) Constants(org.openremote.model.Constants) AssetResource(org.openremote.model.asset.AssetResource) Provider(com.google.inject.Provider) List(java.util.List) AssetBrowser(org.openremote.app.client.assets.browser.AssetBrowser) NumberDatapointArrayMapper(org.openremote.app.client.datapoint.NumberDatapointArrayMapper) Optional(java.util.Optional) Values(org.openremote.model.value.Values) TextUtil.isNullOrEmpty(org.openremote.model.util.TextUtil.isNullOrEmpty) AssetAttribute(org.openremote.model.asset.AssetAttribute) Datapoint(org.openremote.model.datapoint.Datapoint) Collections(java.util.Collections) Simulator(org.openremote.app.client.simulator.Simulator) AttributeExecuteStatus(org.openremote.model.attribute.AttributeExecuteStatus) AttributeView(org.openremote.app.client.assets.attributes.AttributeView) AttributeRef(org.openremote.model.attribute.AttributeRef) AssetAttribute(org.openremote.model.asset.AssetAttribute)

Example 3 with AttributeEvent

use of org.openremote.model.attribute.AttributeEvent 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));
}
Also used : ClientRole(org.openremote.model.security.ClientRole) AuthContext(org.openremote.container.security.AuthContext) AssetDatapointService(org.openremote.manager.datapoint.AssetDatapointService) Date(java.util.Date) CLIENT_EVENT_TOPIC(org.openremote.manager.event.ClientEventService.CLIENT_EVENT_TOPIC) ValidationFailure(org.openremote.model.ValidationFailure) Exchange(org.apache.camel.Exchange) ArrayList(java.util.ArrayList) Level(java.util.logging.Level) Processor(org.apache.camel.Processor) Container(org.openremote.container.Container) ContainerService(org.openremote.container.ContainerService) RulesService(org.openremote.manager.rules.RulesService) AttributeEvent(org.openremote.model.attribute.AttributeEvent) PersistenceService(org.openremote.container.persistence.PersistenceService) AgentService(org.openremote.manager.agent.AgentService) AgentLink.getAgentLink(org.openremote.model.asset.agent.AgentLink.getAgentLink) MessageBrokerService(org.openremote.container.message.MessageBrokerService) ManagerIdentityService(org.openremote.manager.security.ManagerIdentityService) Asset(org.openremote.model.asset.Asset) AssetType(org.openremote.model.asset.AssetType) EntityManager(javax.persistence.EntityManager) Constants(org.openremote.model.Constants) Logger(java.util.logging.Logger) MessageBrokerSetupService(org.openremote.container.message.MessageBrokerSetupService) Collectors(java.util.stream.Collectors) Reason(org.openremote.manager.asset.AssetProcessingException.Reason) AssetResource(org.openremote.model.asset.AssetResource) HEADER_SOURCE(org.openremote.model.attribute.AttributeEvent.HEADER_SOURCE) Value(org.openremote.model.value.Value) ClientEventService(org.openremote.manager.event.ClientEventService) List(java.util.List) RouteBuilder(org.apache.camel.builder.RouteBuilder) TimerService(org.openremote.container.timer.TimerService) Optional(java.util.Optional) Source(org.openremote.model.attribute.AttributeEvent.Source) Values(org.openremote.model.value.Values) AssetAttribute(org.openremote.model.asset.AssetAttribute) Protocol(org.openremote.agent.protocol.Protocol) AttributeExecuteStatus(org.openremote.model.attribute.AttributeExecuteStatus) GlobalLock.withLock(org.openremote.container.concurrent.GlobalLock.withLock) AuthContext(org.openremote.container.security.AuthContext) AttributeEvent(org.openremote.model.attribute.AttributeEvent) Source(org.openremote.model.attribute.AttributeEvent.Source) Date(java.util.Date) ValidationFailure(org.openremote.model.ValidationFailure) AttributeExecuteStatus(org.openremote.model.attribute.AttributeExecuteStatus) AssetAttribute(org.openremote.model.asset.AssetAttribute)

Example 4 with AttributeEvent

use of org.openremote.model.attribute.AttributeEvent in project openremote by openremote.

the class AssetsFacade method dispatch.

@Override
public Assets dispatch(AttributeEvent... events) {
    if (events == null)
        return this;
    for (AttributeEvent event : events) {
        // Check if the asset ID of the event can be found with the default query of this facade
        BaseAssetQuery<RestrictedQuery> checkQuery = query();
        // Set directly on field, as modifying query restrictions is not allowed
        checkQuery.id = event.getEntityId();
        if (assetStorageService.find(checkQuery) == null) {
            throw new IllegalArgumentException("Access to asset not allowed for this rule engine scope: " + event);
        }
        eventConsumer.accept(event);
    }
    return this;
}
Also used : AttributeEvent(org.openremote.model.attribute.AttributeEvent)

Aggregations

AttributeEvent (org.openremote.model.attribute.AttributeEvent)4 ArrayList (java.util.ArrayList)2 List (java.util.List)2 Optional (java.util.Optional)2 Constants (org.openremote.model.Constants)2 AssetAttribute (org.openremote.model.asset.AssetAttribute)2 AssetResource (org.openremote.model.asset.AssetResource)2 AssetType (org.openremote.model.asset.AssetType)2 AttributeExecuteStatus (org.openremote.model.attribute.AttributeExecuteStatus)2 URL (com.google.gwt.http.client.URL)1 Provider (com.google.inject.Provider)1 Collections (java.util.Collections)1 Date (java.util.Date)1 Level (java.util.logging.Level)1 Logger (java.util.logging.Logger)1 Collectors (java.util.stream.Collectors)1 Inject (javax.inject.Inject)1 EntityManager (javax.persistence.EntityManager)1 Exchange (org.apache.camel.Exchange)1 Processor (org.apache.camel.Processor)1