|
|
|
@ -2,36 +2,46 @@ package org.thingsboard.server.service.telemetry; |
|
|
|
|
|
|
|
import com.fasterxml.jackson.core.JsonProcessingException; |
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper; |
|
|
|
import com.google.common.base.Function; |
|
|
|
import com.google.common.util.concurrent.FutureCallback; |
|
|
|
import com.google.common.util.concurrent.Futures; |
|
|
|
import com.google.common.util.concurrent.ListenableFuture; |
|
|
|
import com.hazelcast.util.function.Consumer; |
|
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
|
import org.springframework.stereotype.Service; |
|
|
|
import org.springframework.util.StringUtils; |
|
|
|
import org.thingsboard.server.actors.plugin.ValidationResult; |
|
|
|
import org.thingsboard.server.common.data.DataConstants; |
|
|
|
import org.thingsboard.server.common.data.id.EntityId; |
|
|
|
import org.thingsboard.server.common.data.id.EntityIdFactory; |
|
|
|
import org.thingsboard.server.common.data.kv.Aggregation; |
|
|
|
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
|
|
|
import org.thingsboard.server.common.data.kv.BaseTsKvQuery; |
|
|
|
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; |
|
|
|
import org.thingsboard.server.common.data.kv.TsKvEntry; |
|
|
|
import org.thingsboard.server.common.data.kv.TsKvQuery; |
|
|
|
import org.thingsboard.server.dao.attributes.AttributesService; |
|
|
|
import org.thingsboard.server.dao.timeseries.TimeseriesService; |
|
|
|
import org.thingsboard.server.extensions.api.exception.UnauthorizedException; |
|
|
|
import org.thingsboard.server.extensions.api.plugins.PluginCallback; |
|
|
|
import org.thingsboard.server.extensions.api.plugins.PluginContext; |
|
|
|
import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; |
|
|
|
import org.thingsboard.server.extensions.api.plugins.ws.SessionEvent; |
|
|
|
import org.thingsboard.server.extensions.core.plugin.telemetry.cmd.AttributesSubscriptionCmd; |
|
|
|
import org.thingsboard.server.extensions.core.plugin.telemetry.cmd.GetHistoryCmd; |
|
|
|
import org.thingsboard.server.extensions.core.plugin.telemetry.cmd.SubscriptionCmd; |
|
|
|
import org.thingsboard.server.extensions.core.plugin.telemetry.cmd.TelemetryPluginCmd; |
|
|
|
import org.thingsboard.server.extensions.core.plugin.telemetry.cmd.TelemetryPluginCmdsWrapper; |
|
|
|
import org.thingsboard.server.extensions.core.plugin.telemetry.cmd.TimeseriesSubscriptionCmd; |
|
|
|
import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionErrorCode; |
|
|
|
import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionState; |
|
|
|
import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionType; |
|
|
|
import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionUpdate; |
|
|
|
import org.thingsboard.server.service.security.AccessValidator; |
|
|
|
|
|
|
|
import javax.annotation.Nullable; |
|
|
|
import javax.annotation.PostConstruct; |
|
|
|
import javax.annotation.PreDestroy; |
|
|
|
import java.io.IOException; |
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.Arrays; |
|
|
|
import java.util.Collections; |
|
|
|
import java.util.HashMap; |
|
|
|
import java.util.HashSet; |
|
|
|
@ -41,6 +51,8 @@ import java.util.Optional; |
|
|
|
import java.util.Set; |
|
|
|
import java.util.concurrent.ConcurrentHashMap; |
|
|
|
import java.util.concurrent.ConcurrentMap; |
|
|
|
import java.util.concurrent.ExecutorService; |
|
|
|
import java.util.concurrent.Executors; |
|
|
|
import java.util.stream.Collectors; |
|
|
|
|
|
|
|
/** |
|
|
|
@ -50,6 +62,8 @@ import java.util.stream.Collectors; |
|
|
|
@Slf4j |
|
|
|
public class DefaultTelemetryWebSocketService implements TelemetryWebSocketService { |
|
|
|
|
|
|
|
public static final int DEFAULT_LIMIT = 100; |
|
|
|
public static final Aggregation DEFAULT_AGGREGATION = Aggregation.NONE; |
|
|
|
private static final int UNKNOWN_SUBSCRIPTION_ID = 0; |
|
|
|
private static final String PROCESSING_MSG = "[{}] Processing: {}"; |
|
|
|
private static final ObjectMapper jsonMapper = new ObjectMapper(); |
|
|
|
@ -65,12 +79,29 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi |
|
|
|
@Autowired |
|
|
|
private TelemetryWebSocketMsgEndpoint msgEndpoint; |
|
|
|
|
|
|
|
@Autowired |
|
|
|
private AccessValidator accessValidator; |
|
|
|
|
|
|
|
@Autowired |
|
|
|
private AttributesService attributesService; |
|
|
|
|
|
|
|
@Autowired |
|
|
|
private TimeseriesService tsService; |
|
|
|
|
|
|
|
private ExecutorService executor; |
|
|
|
|
|
|
|
@PostConstruct |
|
|
|
public void initExecutor() { |
|
|
|
executor = Executors.newSingleThreadExecutor(); |
|
|
|
} |
|
|
|
|
|
|
|
@PreDestroy |
|
|
|
public void shutdownExecutor() { |
|
|
|
if (executor != null) { |
|
|
|
executor.shutdownNow(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void handleWebSocketSessionEvent(TelemetryWebSocketSessionRef sessionRef, SessionEvent event) { |
|
|
|
String sessionId = sessionRef.getSessionId(); |
|
|
|
@ -169,44 +200,190 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi |
|
|
|
}; |
|
|
|
|
|
|
|
if (StringUtils.isEmpty(cmd.getScope())) { |
|
|
|
//ValidationCallback?
|
|
|
|
ctx.loadAttributes(entityId, Arrays.asList(DataConstants.allScopes()), keys, callback); |
|
|
|
accessValidator.validate(sessionRef.getSecurityCtx(), entityId, getAttributesFetchCallback(entityId, keys, callback)); |
|
|
|
} else { |
|
|
|
ctx.loadAttributes(entityId, cmd.getScope(), keys, callback); |
|
|
|
accessValidator.validate(sessionRef.getSecurityCtx(), entityId, getAttributesFetchCallback(entityId, cmd.getScope(), keys, callback)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void handleWsHistoryCmd(TelemetryWebSocketSessionRef sessionRef, GetHistoryCmd cmd) { |
|
|
|
String sessionId = sessionRef.getSessionId(); |
|
|
|
WsSessionMetaData sessionMD = wsSessionsMap.get(sessionId); |
|
|
|
if (sessionMD == null) { |
|
|
|
log.warn("[{}] Session meta data not found. ", sessionId); |
|
|
|
SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, |
|
|
|
SESSION_META_DATA_NOT_FOUND); |
|
|
|
sendWsMsg(sessionRef, update); |
|
|
|
return; |
|
|
|
} |
|
|
|
if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty() || cmd.getEntityType() == null || cmd.getEntityType().isEmpty()) { |
|
|
|
SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, |
|
|
|
"Device id is empty!"); |
|
|
|
sendWsMsg(sessionRef, update); |
|
|
|
return; |
|
|
|
} |
|
|
|
if (cmd.getKeys() == null || cmd.getKeys().isEmpty()) { |
|
|
|
SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, |
|
|
|
"Keys are empty!"); |
|
|
|
sendWsMsg(sessionRef, update); |
|
|
|
return; |
|
|
|
} |
|
|
|
EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId()); |
|
|
|
List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet())); |
|
|
|
List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))) |
|
|
|
.collect(Collectors.toList()); |
|
|
|
|
|
|
|
FutureCallback<List<TsKvEntry>> callback = new FutureCallback<List<TsKvEntry>>() { |
|
|
|
@Override |
|
|
|
public void onSuccess(List<TsKvEntry> data) { |
|
|
|
sendWsMsg(sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data)); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void onFailure(Throwable e) { |
|
|
|
SubscriptionUpdate update; |
|
|
|
if (UnauthorizedException.class.isInstance(e)) { |
|
|
|
update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.UNAUTHORIZED, |
|
|
|
SubscriptionErrorCode.UNAUTHORIZED.getDefaultMsg()); |
|
|
|
} else { |
|
|
|
update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, |
|
|
|
FAILED_TO_FETCH_DATA); |
|
|
|
} |
|
|
|
sendWsMsg(sessionRef, update); |
|
|
|
} |
|
|
|
}; |
|
|
|
accessValidator.validate(sessionRef.getSecurityCtx(), entityId, |
|
|
|
on(r -> Futures.addCallback(tsService.findAll(entityId, queries), callback, executor), callback::onFailure)); |
|
|
|
} |
|
|
|
|
|
|
|
private void handleWsAttributesSubscription(PluginContext ctx, PluginWebsocketSessionRef sessionRef, |
|
|
|
private void handleWsAttributesSubscription(TelemetryWebSocketSessionRef sessionRef, |
|
|
|
AttributesSubscriptionCmd cmd, String sessionId, EntityId entityId) { |
|
|
|
PluginCallback<List<AttributeKvEntry>> callback = new PluginCallback<List<AttributeKvEntry>>() { |
|
|
|
FutureCallback<List<AttributeKvEntry>> callback = new FutureCallback<List<AttributeKvEntry>>() { |
|
|
|
@Override |
|
|
|
public void onSuccess(PluginContext ctx, List<AttributeKvEntry> data) { |
|
|
|
public void onSuccess(List<AttributeKvEntry> data) { |
|
|
|
List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList()); |
|
|
|
sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), attributesData)); |
|
|
|
sendWsMsg(sessionRef, new SubscriptionUpdate(cmd.getCmdId(), attributesData)); |
|
|
|
|
|
|
|
Map<String, Long> subState = new HashMap<>(attributesData.size()); |
|
|
|
attributesData.forEach(v -> subState.put(v.getKey(), v.getTs())); |
|
|
|
|
|
|
|
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), entityId, SubscriptionType.ATTRIBUTES, true, subState, cmd.getScope()); |
|
|
|
subscriptionManager.addLocalWsSubscription(ctx, sessionId, entityId, sub); |
|
|
|
subscriptionManager.addLocalWsSubscription(sessionId, entityId, sub); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void onFailure(PluginContext ctx, Exception e) { |
|
|
|
public void onFailure(Throwable e) { |
|
|
|
log.error(FAILED_TO_FETCH_ATTRIBUTES, e); |
|
|
|
SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, |
|
|
|
FAILED_TO_FETCH_ATTRIBUTES); |
|
|
|
sendWsMsg(ctx, sessionRef, update); |
|
|
|
sendWsMsg(sessionRef, update); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
if (StringUtils.isEmpty(cmd.getScope())) { |
|
|
|
ctx.loadAttributes(entityId, Arrays.asList(DataConstants.allScopes()), callback); |
|
|
|
accessValidator.validate(sessionRef.getSecurityCtx(), entityId, getAttributesFetchCallback(entityId, callback)); |
|
|
|
} else { |
|
|
|
accessValidator.validate(sessionRef.getSecurityCtx(), entityId, getAttributesFetchCallback(entityId, cmd.getScope(), callback)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void handleWsTimeseriesSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, TimeseriesSubscriptionCmd cmd) { |
|
|
|
String sessionId = sessionRef.getSessionId(); |
|
|
|
log.debug("[{}] Processing: {}", sessionId, cmd); |
|
|
|
|
|
|
|
if (validateSessionMetadata(sessionRef, cmd, sessionId)) { |
|
|
|
if (cmd.isUnsubscribe()) { |
|
|
|
unsubscribe(sessionRef, cmd, sessionId); |
|
|
|
} else if (validateSubscriptionCmd(sessionRef, cmd)) { |
|
|
|
EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId()); |
|
|
|
Optional<Set<String>> keysOptional = getKeys(cmd); |
|
|
|
|
|
|
|
if (keysOptional.isPresent()) { |
|
|
|
handleWsTimeseriesSubscriptionByKeys(sessionRef, cmd, sessionId, entityId); |
|
|
|
} else { |
|
|
|
handleWsTimeseriesSubscription(sessionRef, cmd, sessionId, entityId); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void handleWsTimeseriesSubscriptionByKeys(TelemetryWebSocketSessionRef sessionRef, |
|
|
|
TimeseriesSubscriptionCmd cmd, String sessionId, EntityId entityId) { |
|
|
|
long startTs; |
|
|
|
if (cmd.getTimeWindow() > 0) { |
|
|
|
List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet())); |
|
|
|
log.debug("[{}] fetching timeseries data for last {} ms for keys: ({}) for device : {}", sessionId, cmd.getTimeWindow(), cmd.getKeys(), entityId); |
|
|
|
startTs = cmd.getStartTs(); |
|
|
|
long endTs = cmd.getStartTs() + cmd.getTimeWindow(); |
|
|
|
List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, cmd.getInterval(), |
|
|
|
getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))).collect(Collectors.toList()); |
|
|
|
|
|
|
|
final FutureCallback<List<TsKvEntry>> callback = getSubscriptionCallback(sessionRef, cmd, sessionId, entityId, startTs, keys); |
|
|
|
accessValidator.validate(sessionRef.getSecurityCtx(), entityId, |
|
|
|
on(r -> Futures.addCallback(tsService.findAll(entityId, queries), callback, executor), callback::onFailure)); |
|
|
|
} else { |
|
|
|
ctx.loadAttributes(entityId, cmd.getScope(), callback); |
|
|
|
List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet())); |
|
|
|
startTs = System.currentTimeMillis(); |
|
|
|
log.debug("[{}] fetching latest timeseries data for keys: ({}) for device : {}", sessionId, cmd.getKeys(), entityId); |
|
|
|
final FutureCallback<List<TsKvEntry>> callback = getSubscriptionCallback(sessionRef, cmd, sessionId, entityId, startTs, keys); |
|
|
|
accessValidator.validate(sessionRef.getSecurityCtx(), entityId, |
|
|
|
on(r -> Futures.addCallback(tsService.findLatest(entityId, keys), callback, executor), callback::onFailure)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void handleWsTimeseriesSubscription(TelemetryWebSocketSessionRef sessionRef, |
|
|
|
TimeseriesSubscriptionCmd cmd, String sessionId, EntityId entityId) { |
|
|
|
FutureCallback<List<TsKvEntry>> callback = new FutureCallback<List<TsKvEntry>>() { |
|
|
|
@Override |
|
|
|
public void onSuccess(List<TsKvEntry> data) { |
|
|
|
sendWsMsg(sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data)); |
|
|
|
Map<String, Long> subState = new HashMap<>(data.size()); |
|
|
|
data.forEach(v -> subState.put(v.getKey(), v.getTs())); |
|
|
|
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), entityId, SubscriptionType.TIMESERIES, true, subState, cmd.getScope()); |
|
|
|
subscriptionManager.addLocalWsSubscription(sessionId, entityId, sub); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void onFailure(Throwable e) { |
|
|
|
SubscriptionUpdate update; |
|
|
|
if (UnauthorizedException.class.isInstance(e)) { |
|
|
|
update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.UNAUTHORIZED, |
|
|
|
SubscriptionErrorCode.UNAUTHORIZED.getDefaultMsg()); |
|
|
|
} else { |
|
|
|
update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, |
|
|
|
FAILED_TO_FETCH_DATA); |
|
|
|
} |
|
|
|
sendWsMsg(sessionRef, update); |
|
|
|
} |
|
|
|
}; |
|
|
|
accessValidator.validate(sessionRef.getSecurityCtx(), entityId, |
|
|
|
on(r -> Futures.addCallback(tsService.findAllLatest(entityId), callback, executor), callback::onFailure)); |
|
|
|
} |
|
|
|
|
|
|
|
private FutureCallback<List<TsKvEntry>> getSubscriptionCallback(final TelemetryWebSocketSessionRef sessionRef, final TimeseriesSubscriptionCmd cmd, final String sessionId, final EntityId entityId, final long startTs, final List<String> keys) { |
|
|
|
return new FutureCallback<List<TsKvEntry>>() { |
|
|
|
@Override |
|
|
|
public void onSuccess(List<TsKvEntry> data) { |
|
|
|
sendWsMsg(sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data)); |
|
|
|
|
|
|
|
Map<String, Long> subState = new HashMap<>(keys.size()); |
|
|
|
keys.forEach(key -> subState.put(key, startTs)); |
|
|
|
data.forEach(v -> subState.put(v.getKey(), v.getTs())); |
|
|
|
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), entityId, SubscriptionType.TIMESERIES, false, subState, cmd.getScope()); |
|
|
|
subscriptionManager.addLocalWsSubscription(sessionId, entityId, sub); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void onFailure(Throwable e) { |
|
|
|
log.error(FAILED_TO_FETCH_DATA, e); |
|
|
|
SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, |
|
|
|
FAILED_TO_FETCH_DATA); |
|
|
|
sendWsMsg(sessionRef, update); |
|
|
|
} |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
private void unsubscribe(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) { |
|
|
|
if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) { |
|
|
|
subscriptionManager.cleanupLocalWsSessionSubscriptions(sessionRef, sessionId); |
|
|
|
@ -258,4 +435,105 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private ListenableFuture<List<AttributeKvEntry>> mergeAllAttributesFutures(List<ListenableFuture<List<AttributeKvEntry>>> futures) { |
|
|
|
return Futures.transform(Futures.successfulAsList(futures), |
|
|
|
(Function<? super List<List<AttributeKvEntry>>, ? extends List<AttributeKvEntry>>) input -> { |
|
|
|
List<AttributeKvEntry> tmp = new ArrayList<>(); |
|
|
|
if (input != null) { |
|
|
|
input.forEach(tmp::addAll); |
|
|
|
} |
|
|
|
return tmp; |
|
|
|
}, executor); |
|
|
|
} |
|
|
|
|
|
|
|
private <T> FutureCallback<ValidationResult> getAttributesFetchCallback(final EntityId entityId, final List<String> keys, final FutureCallback<List<AttributeKvEntry>> callback) { |
|
|
|
return new FutureCallback<ValidationResult>() { |
|
|
|
@Override |
|
|
|
public void onSuccess(@Nullable ValidationResult result) { |
|
|
|
List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>(); |
|
|
|
for (String scope : DataConstants.allScopes()) { |
|
|
|
futures.add(attributesService.find(entityId, scope, keys)); |
|
|
|
} |
|
|
|
|
|
|
|
ListenableFuture<List<AttributeKvEntry>> future = mergeAllAttributesFutures(futures); |
|
|
|
Futures.addCallback(future, callback); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void onFailure(Throwable t) { |
|
|
|
callback.onFailure(t); |
|
|
|
} |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
private <T> FutureCallback<ValidationResult> getAttributesFetchCallback(final EntityId entityId, final String scope, final List<String> keys, final FutureCallback<List<AttributeKvEntry>> callback) { |
|
|
|
return new FutureCallback<ValidationResult>() { |
|
|
|
@Override |
|
|
|
public void onSuccess(@Nullable ValidationResult result) { |
|
|
|
Futures.addCallback(attributesService.find(entityId, scope, keys), callback); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void onFailure(Throwable t) { |
|
|
|
callback.onFailure(t); |
|
|
|
} |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
private <T> FutureCallback<ValidationResult> getAttributesFetchCallback(final EntityId entityId, final FutureCallback<List<AttributeKvEntry>> callback) { |
|
|
|
return new FutureCallback<ValidationResult>() { |
|
|
|
@Override |
|
|
|
public void onSuccess(@Nullable ValidationResult result) { |
|
|
|
List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>(); |
|
|
|
for (String scope : DataConstants.allScopes()) { |
|
|
|
futures.add(attributesService.findAll(entityId, scope)); |
|
|
|
} |
|
|
|
|
|
|
|
ListenableFuture<List<AttributeKvEntry>> future = mergeAllAttributesFutures(futures); |
|
|
|
Futures.addCallback(future, callback); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void onFailure(Throwable t) { |
|
|
|
callback.onFailure(t); |
|
|
|
} |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
private <T> FutureCallback<ValidationResult> getAttributesFetchCallback(final EntityId entityId, final String scope, final FutureCallback<List<AttributeKvEntry>> callback) { |
|
|
|
return new FutureCallback<ValidationResult>() { |
|
|
|
@Override |
|
|
|
public void onSuccess(@Nullable ValidationResult result) { |
|
|
|
Futures.addCallback(attributesService.findAll(entityId, scope), callback); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void onFailure(Throwable t) { |
|
|
|
callback.onFailure(t); |
|
|
|
} |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
private FutureCallback<ValidationResult> on(Consumer<ValidationResult> success, Consumer<Throwable> failure) { |
|
|
|
return new FutureCallback<ValidationResult>() { |
|
|
|
@Override |
|
|
|
public void onSuccess(@Nullable ValidationResult result) { |
|
|
|
success.accept(result); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void onFailure(Throwable t) { |
|
|
|
failure.accept(t); |
|
|
|
} |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static Aggregation getAggregation(String agg) { |
|
|
|
return StringUtils.isEmpty(agg) ? DEFAULT_AGGREGATION : Aggregation.valueOf(agg); |
|
|
|
} |
|
|
|
|
|
|
|
private int getLimit(int limit) { |
|
|
|
return limit == 0 ? DEFAULT_LIMIT : limit; |
|
|
|
} |
|
|
|
} |
|
|
|
|