Major refactoring of plugin context

This commit is contained in:
Andrew Shvayka 2017-02-21 19:29:00 +02:00
parent 2accabf0b0
commit 301f106e54
11 changed files with 294 additions and 168 deletions

View File

@ -1,12 +1,12 @@
/** /**
* Copyright © 2016-2017 The Thingsboard Authors * Copyright © 2016-2017 The Thingsboard Authors
* <p> *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* <p> *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* <p> *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -90,121 +90,102 @@ public final class PluginProcessingContext implements PluginContext {
} }
@Override @Override
public void saveAttributes(DeviceId deviceId, String scope, List<AttributeKvEntry> attributes, PluginCallback<Void> callback) { public void saveAttributes(final TenantId tenantId, final DeviceId deviceId, final String scope, final List<AttributeKvEntry> attributes, final PluginCallback<Void> callback) {
validate(deviceId); validate(deviceId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.attributesService.save(deviceId, scope, attributes);
Futures.addCallback(rsListFuture, getListCallback(callback, v -> {
onDeviceAttributesChanged(deviceId, scope, attributes);
return null;
}), executor);
}
@Override
public void removeAttributes(DeviceId deviceId, String scope, List<String> keys, PluginCallback<Void> callback) {
validate(deviceId);
ListenableFuture<List<ResultSet>> future = pluginCtx.attributesService.removeAll(deviceId, scope, keys);
Futures.addCallback(future, getCallback(callback, v -> null), executor);
onDeviceAttributesDeleted(tenantId, deviceId, keys.stream().map(key -> new AttributeKey(scope, key)).collect(Collectors.toSet()));
}
@Override
public void saveAttributesByDevice(TenantId tenantId, DeviceId deviceId, String scope, List<AttributeKvEntry> attributes, PluginCallback<Void> callback) {
validate(deviceId);
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.attributesService.save(deviceId, scope, attributes); ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.attributesService.save(deviceId, scope, attributes);
Futures.addCallback(rsListFuture, getListCallback(callback, v -> { Futures.addCallback(rsListFuture, getListCallback(callback, v -> {
onDeviceAttributesChanged(tenantId, deviceId, scope, attributes); onDeviceAttributesChanged(tenantId, deviceId, scope, attributes);
return null; return null;
}), executor); }), executor);
}));
} }
@Override @Override
public void removeAttributesByDevice(TenantId tenantId, DeviceId deviceId, String scope, List<String> keys, PluginCallback<Void> callback) { public void removeAttributes(final TenantId tenantId, final DeviceId deviceId, final String scope, final List<String> keys, final PluginCallback<Void> callback) {
validate(deviceId); validate(deviceId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> future = pluginCtx.attributesService.removeAll(deviceId, scope, keys); ListenableFuture<List<ResultSet>> future = pluginCtx.attributesService.removeAll(deviceId, scope, keys);
Futures.addCallback(future, getCallback(callback, v -> null), executor); Futures.addCallback(future, getCallback(callback, v -> null), executor);
onDeviceAttributesDeleted(tenantId, deviceId, keys.stream().map(key -> new AttributeKey(scope, key)).collect(Collectors.toSet())); onDeviceAttributesDeleted(tenantId, deviceId, keys.stream().map(key -> new AttributeKey(scope, key)).collect(Collectors.toSet()));
}));
} }
@Override @Override
public void loadAttribute(DeviceId deviceId, String attributeType, String attributeKey, PluginCallback<Optional<AttributeKvEntry>> callback) { public void loadAttribute(DeviceId deviceId, String attributeType, String attributeKey, final PluginCallback<Optional<AttributeKvEntry>> callback) {
validate(deviceId); validate(deviceId, new ValidationCallback(callback, ctx -> {
ListenableFuture<Optional<AttributeKvEntry>> future = pluginCtx.attributesService.find(deviceId, attributeType, attributeKey); ListenableFuture<Optional<AttributeKvEntry>> future = pluginCtx.attributesService.find(deviceId, attributeType, attributeKey);
Futures.addCallback(future, getCallback(callback, v -> v), executor); Futures.addCallback(future, getCallback(callback, v -> v), executor);
}));
} }
@Override @Override
public void loadAttributes(DeviceId deviceId, String attributeType, Collection<String> attributeKeys, PluginCallback<List<AttributeKvEntry>> callback) { public void loadAttributes(DeviceId deviceId, String attributeType, Collection<String> attributeKeys, final PluginCallback<List<AttributeKvEntry>> callback) {
validate(deviceId); validate(deviceId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<AttributeKvEntry>> future = pluginCtx.attributesService.find(deviceId, attributeType, attributeKeys); ListenableFuture<List<AttributeKvEntry>> future = pluginCtx.attributesService.find(deviceId, attributeType, attributeKeys);
Futures.addCallback(future, getCallback(callback, v -> v), executor); Futures.addCallback(future, getCallback(callback, v -> v), executor);
}));
} }
@Override @Override
public void loadAttributes(DeviceId deviceId, String attributeType, PluginCallback<List<AttributeKvEntry>> callback) { public void loadAttributes(DeviceId deviceId, String attributeType, PluginCallback<List<AttributeKvEntry>> callback) {
validate(deviceId); validate(deviceId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<AttributeKvEntry>> future = pluginCtx.attributesService.findAll(deviceId, attributeType); ListenableFuture<List<AttributeKvEntry>> future = pluginCtx.attributesService.findAll(deviceId, attributeType);
Futures.addCallback(future, getCallback(callback, v -> v), executor); Futures.addCallback(future, getCallback(callback, v -> v), executor);
}));
} }
@Override @Override
public void loadAttributes(DeviceId deviceId, Collection<String> attributeTypes, PluginCallback<List<AttributeKvEntry>> callback) { public void loadAttributes(final DeviceId deviceId, final Collection<String> attributeTypes, final PluginCallback<List<AttributeKvEntry>> callback) {
validate(deviceId); validate(deviceId, new ValidationCallback(callback, ctx -> {
List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>(); List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>();
attributeTypes.forEach(attributeType -> futures.add(pluginCtx.attributesService.findAll(deviceId, attributeType))); attributeTypes.forEach(attributeType -> futures.add(pluginCtx.attributesService.findAll(deviceId, attributeType)));
convertFuturesAndAddCallback(callback, futures); convertFuturesAndAddCallback(callback, futures);
}));
} }
@Override @Override
public void loadAttributes(DeviceId deviceId, Collection<String> attributeTypes, Collection<String> attributeKeys, PluginCallback<List<AttributeKvEntry>> callback) { public void loadAttributes(final DeviceId deviceId, final Collection<String> attributeTypes, final Collection<String> attributeKeys, final PluginCallback<List<AttributeKvEntry>> callback) {
validate(deviceId); validate(deviceId, new ValidationCallback(callback, ctx -> {
List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>(); List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>();
attributeTypes.forEach(attributeType -> futures.add(pluginCtx.attributesService.find(deviceId, attributeType, attributeKeys))); attributeTypes.forEach(attributeType -> futures.add(pluginCtx.attributesService.find(deviceId, attributeType, attributeKeys)));
convertFuturesAndAddCallback(callback, futures); convertFuturesAndAddCallback(callback, futures);
} }));
private void convertFuturesAndAddCallback(PluginCallback<List<AttributeKvEntry>> callback, List<ListenableFuture<List<AttributeKvEntry>>> futures) {
ListenableFuture<List<AttributeKvEntry>> future = Futures.transform(Futures.successfulAsList(futures),
(Function<? super List<List<AttributeKvEntry>>, ? extends List<AttributeKvEntry>>) input -> {
List<AttributeKvEntry> result = new ArrayList<>();
input.forEach(r -> result.addAll(r));
return result;
}, executor);
Futures.addCallback(future, getCallback(callback, v -> v), executor);
} }
@Override @Override
public void saveTsData(DeviceId deviceId, TsKvEntry entry, PluginCallback<Void> callback) { public void saveTsData(final DeviceId deviceId, final TsKvEntry entry, final PluginCallback<Void> callback) {
validate(deviceId); validate(deviceId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.save(DataConstants.DEVICE, deviceId, entry); ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.save(DataConstants.DEVICE, deviceId, entry);
Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor); Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor);
}));
} }
@Override @Override
public void saveTsData(DeviceId deviceId, List<TsKvEntry> entries, PluginCallback<Void> callback) { public void saveTsData(final DeviceId deviceId, final List<TsKvEntry> entries, final PluginCallback<Void> callback) {
validate(deviceId); validate(deviceId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.save(DataConstants.DEVICE, deviceId, entries); ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.save(DataConstants.DEVICE, deviceId, entries);
Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor); Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor);
}));
} }
@Override @Override
public void loadTimeseries(DeviceId deviceId, List<TsKvQuery> queries, PluginCallback<List<TsKvEntry>> callback) { public void loadTimeseries(final DeviceId deviceId, final List<TsKvQuery> queries, final PluginCallback<List<TsKvEntry>> callback) {
validate(deviceId); validate(deviceId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<TsKvEntry>> future = pluginCtx.tsService.findAll(DataConstants.DEVICE, deviceId, queries); ListenableFuture<List<TsKvEntry>> future = pluginCtx.tsService.findAll(DataConstants.DEVICE, deviceId, queries);
Futures.addCallback(future, getCallback(callback, v -> v), executor); Futures.addCallback(future, getCallback(callback, v -> v), executor);
}));
} }
@Override @Override
public void loadLatestTimeseries(DeviceId deviceId, PluginCallback<List<TsKvEntry>> callback) { public void loadLatestTimeseries(final DeviceId deviceId, final PluginCallback<List<TsKvEntry>> callback) {
validate(deviceId); validate(deviceId, new ValidationCallback(callback, ctx -> {
ResultSetFuture future = pluginCtx.tsService.findAllLatest(DataConstants.DEVICE, deviceId); ResultSetFuture future = pluginCtx.tsService.findAllLatest(DataConstants.DEVICE, deviceId);
Futures.addCallback(future, getCallback(callback, pluginCtx.tsService::convertResultSetToTsKvEntryList), executor); Futures.addCallback(future, getCallback(callback, pluginCtx.tsService::convertResultSetToTsKvEntryList), executor);
}));
} }
@Override @Override
public void loadLatestTimeseries(DeviceId deviceId, Collection<String> keys, PluginCallback<List<TsKvEntry>> callback) { public void loadLatestTimeseries(final DeviceId deviceId, final Collection<String> keys, final PluginCallback<List<TsKvEntry>> callback) {
validate(deviceId); validate(deviceId, new ValidationCallback(callback, ctx -> {
ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.findLatest(DataConstants.DEVICE, deviceId, keys); ListenableFuture<List<ResultSet>> rsListFuture = pluginCtx.tsService.findLatest(DataConstants.DEVICE, deviceId, keys);
Futures.addCallback(rsListFuture, getListCallback(callback, rsList -> Futures.addCallback(rsListFuture, getListCallback(callback, rsList ->
{ {
@ -217,6 +198,7 @@ public final class PluginProcessingContext implements PluginContext {
} }
return result; return result;
}), executor); }), executor);
}));
} }
@Override @Override
@ -224,15 +206,6 @@ public final class PluginProcessingContext implements PluginContext {
pluginCtx.parentActor.tell(msg, ActorRef.noSender()); pluginCtx.parentActor.tell(msg, ActorRef.noSender());
} }
@Override
public boolean checkAccess(DeviceId deviceId) {
try {
return validate(deviceId);
} catch (IllegalStateException | IllegalArgumentException e) {
return false;
}
}
@Override @Override
public PluginId getPluginId() { public PluginId getPluginId() {
return pluginCtx.pluginId; return pluginCtx.pluginId;
@ -273,7 +246,11 @@ public final class PluginProcessingContext implements PluginContext {
return new FutureCallback<R>() { return new FutureCallback<R>() {
@Override @Override
public void onSuccess(@Nullable R result) { public void onSuccess(@Nullable R result) {
try {
pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, transformer.apply(result)), ActorRef.noSender()); pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, transformer.apply(result)), ActorRef.noSender());
} catch (Exception e) {
pluginCtx.self().tell(PluginCallbackMessage.onError(callback, e), ActorRef.noSender());
}
} }
@Override @Override
@ -287,28 +264,36 @@ public final class PluginProcessingContext implements PluginContext {
}; };
} }
// TODO: replace with our own exceptions @Override
private boolean validate(DeviceId deviceId, PluginCallback<Device> callback) { public void checkAccess(DeviceId deviceId, PluginCallback<Void> callback) {
validate(deviceId, new ValidationCallback(callback, ctx -> callback.onSuccess(ctx, null)));
}
private void validate(DeviceId deviceId, ValidationCallback callback) {
if (securityCtx.isPresent()) { if (securityCtx.isPresent()) {
final PluginApiCallSecurityContext ctx = securityCtx.get(); final PluginApiCallSecurityContext ctx = securityCtx.get();
if (ctx.isTenantAdmin() || ctx.isCustomerUser()) { if (ctx.isTenantAdmin() || ctx.isCustomerUser()) {
ListenableFuture<Device> device = pluginCtx.deviceService.findDeviceById(deviceId); ListenableFuture<Device> deviceFuture = pluginCtx.deviceService.findDeviceByIdAsync(deviceId);
Futures.addCallback(device, ); Futures.addCallback(deviceFuture, getCallback(callback, device -> {
if (device == null) { if (device == null) {
throw new IllegalStateException("Device not found!"); return Boolean.FALSE;
} else { } else {
if (!device.getTenantId().equals(ctx.getTenantId())) { if (!device.getTenantId().equals(ctx.getTenantId())) {
throw new IllegalArgumentException("Device belongs to different tenant!"); return Boolean.FALSE;
} else if (ctx.isCustomerUser() && !device.getCustomerId().equals(ctx.getCustomerId())) { } else if (ctx.isCustomerUser() && !device.getCustomerId().equals(ctx.getCustomerId())) {
throw new IllegalArgumentException("Device belongs to different customer!"); return Boolean.FALSE;
} else {
return Boolean.TRUE;
} }
} }
}));
} else {
callback.onSuccess(this, Boolean.FALSE);
}
} else { } else {
return false; callback.onSuccess(this, Boolean.TRUE);
} }
} }
return true;
}
@Override @Override
public Optional<ServerAddress> resolve(DeviceId deviceId) { public Optional<ServerAddress> resolve(DeviceId deviceId) {
@ -338,4 +323,15 @@ public final class PluginProcessingContext implements PluginContext {
public void scheduleTimeoutMsg(TimeoutMsg msg) { public void scheduleTimeoutMsg(TimeoutMsg msg) {
pluginCtx.scheduleTimeoutMsg(msg); pluginCtx.scheduleTimeoutMsg(msg);
} }
private void convertFuturesAndAddCallback(PluginCallback<List<AttributeKvEntry>> callback, List<ListenableFuture<List<AttributeKvEntry>>> futures) {
ListenableFuture<List<AttributeKvEntry>> future = Futures.transform(Futures.successfulAsList(futures),
(Function<? super List<List<AttributeKvEntry>>, ? extends List<AttributeKvEntry>>) input -> {
List<AttributeKvEntry> result = new ArrayList<>();
input.forEach(r -> result.addAll(r));
return result;
}, executor);
Futures.addCallback(future, getCallback(callback, v -> v), executor);
}
} }

View File

@ -0,0 +1,49 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.plugin;
import com.hazelcast.util.function.Consumer;
import org.thingsboard.server.extensions.api.exception.UnauthorizedException;
import org.thingsboard.server.extensions.api.plugins.PluginCallback;
import org.thingsboard.server.extensions.api.plugins.PluginContext;
/**
* Created by ashvayka on 21.02.17.
*/
public class ValidationCallback implements PluginCallback<Boolean> {
private final PluginCallback<?> callback;
private final Consumer<PluginContext> action;
public ValidationCallback(PluginCallback<?> callback, Consumer<PluginContext> action) {
this.callback = callback;
this.action = action;
}
@Override
public void onSuccess(PluginContext ctx, Boolean value) {
if (value) {
action.accept(ctx);
} else {
onFailure(ctx, new UnauthorizedException());
}
}
@Override
public void onFailure(PluginContext ctx, Exception e) {
callback.onFailure(ctx, e);
}
}

View File

@ -16,17 +16,22 @@
package org.thingsboard.server.dao; package org.thingsboard.server.dao;
import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.Statement; import com.datastax.driver.core.Statement;
import com.datastax.driver.core.querybuilder.QueryBuilder; import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select; import com.datastax.driver.core.querybuilder.Select;
import com.datastax.driver.core.utils.UUIDs; import com.datastax.driver.core.utils.UUIDs;
import com.datastax.driver.mapping.Mapper; import com.datastax.driver.mapping.Mapper;
import com.datastax.driver.mapping.Result; import com.datastax.driver.mapping.Result;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseEntity;
import org.thingsboard.server.dao.model.wrapper.EntityResultSet; import org.thingsboard.server.dao.model.wrapper.EntityResultSet;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import javax.annotation.Nullable;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -72,6 +77,27 @@ public abstract class AbstractModelDao<T extends BaseEntity<?>> extends Abstract
return object; return object;
} }
protected ListenableFuture<T> findOneByStatementAsync(Statement statement) {
if (statement != null) {
statement.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel());
ResultSetFuture resultSetFuture = getSession().executeAsync(statement);
ListenableFuture<T> result = Futures.transform(resultSetFuture, new Function<ResultSet, T>() {
@Nullable
@Override
public T apply(@Nullable ResultSet resultSet) {
Result<T> result = getMapper().map(resultSet);
if (result != null) {
return result.one();
} else {
return null;
}
}
});
return result;
}
return Futures.immediateFuture(null);
}
protected Statement getSaveQuery(T dto) { protected Statement getSaveQuery(T dto) {
return getMapper().saveQuery(dto); return getMapper().saveQuery(dto);
} }
@ -100,6 +126,14 @@ public abstract class AbstractModelDao<T extends BaseEntity<?>> extends Abstract
return findOneByStatement(query); return findOneByStatement(query);
} }
public ListenableFuture<T> findByIdAsync(UUID key) {
log.debug("Get entity by key {}", key);
Select.Where query = select().from(getColumnFamilyName()).where(eq(ModelConstants.ID_PROPERTY, key));
log.trace("Execute query {}", query);
return findOneByStatementAsync(query);
}
public ResultSet removeById(UUID key) { public ResultSet removeById(UUID key) {
Statement delete = QueryBuilder.delete().all().from(getColumnFamilyName()).where(eq(ModelConstants.ID_PROPERTY, key)); Statement delete = QueryBuilder.delete().all().from(getColumnFamilyName()).where(eq(ModelConstants.ID_PROPERTY, key));
log.debug("Remove request: {}", delete.toString()); log.debug("Remove request: {}", delete.toString());

View File

@ -16,6 +16,7 @@
package org.thingsboard.server.dao; package org.thingsboard.server.dao;
import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.ResultSet;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -26,6 +27,8 @@ public interface Dao<T> {
T findById(UUID id); T findById(UUID id);
ListenableFuture<T> findByIdAsync(UUID id);
T save(T t); T save(T t);
ResultSet removeById(UUID id); ResultSet removeById(UUID id);

View File

@ -15,6 +15,9 @@
*/ */
package org.thingsboard.server.dao.device; package org.thingsboard.server.dao.device;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -70,6 +73,14 @@ public class DeviceServiceImpl implements DeviceService {
return getData(deviceEntity); return getData(deviceEntity);
} }
@Override
public ListenableFuture<Device> findDeviceByIdAsync(DeviceId deviceId) {
log.trace("Executing findDeviceById [{}]", deviceId);
validateId(deviceId, "Incorrect deviceId " + deviceId);
ListenableFuture<DeviceEntity> deviceEntity = deviceDao.findByIdAsync(deviceId.getId());
return Futures.transform(deviceEntity, (Function<? super DeviceEntity, ? extends Device>) input -> getData(input));
}
@Override @Override
public Optional<Device> findDeviceByTenantIdAndName(TenantId tenantId, String name) { public Optional<Device> findDeviceByTenantIdAndName(TenantId tenantId, String name) {
log.trace("Executing findDeviceByTenantIdAndName [{}][{}]", tenantId, name); log.trace("Executing findDeviceByTenantIdAndName [{}][{}]", tenantId, name);

View File

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2017 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.extensions.api.exception;
/**
* Created by ashvayka on 21.02.17.
*/
public class UnauthorizedException extends Exception {
}

View File

@ -42,7 +42,7 @@ public interface PluginContext {
void reply(PluginToRuleMsg<?> msg); void reply(PluginToRuleMsg<?> msg);
boolean checkAccess(DeviceId deviceId); void checkAccess(DeviceId deviceId, PluginCallback<Void> callback);
Optional<PluginApiCallSecurityContext> getSecurityCtx(); Optional<PluginApiCallSecurityContext> getSecurityCtx();
@ -92,13 +92,9 @@ public interface PluginContext {
Attributes API Attributes API
*/ */
void saveAttributes(DeviceId deviceId, String attributeType, List<AttributeKvEntry> attributes, PluginCallback<Void> callback); void saveAttributes(TenantId tenantId, DeviceId deviceId, String attributeType, List<AttributeKvEntry> attributes, PluginCallback<Void> callback);
void removeAttributes(DeviceId deviceId, String scope, List<String> attributeKeys, PluginCallback<Void> callback); void removeAttributes(TenantId tenantId, DeviceId deviceId, String scope, List<String> attributeKeys, PluginCallback<Void> callback);
void saveAttributesByDevice(TenantId tenantId, DeviceId deviceId, String attributeType, List<AttributeKvEntry> attributes, PluginCallback<Void> callback);
void removeAttributesByDevice(TenantId tenantId, DeviceId deviceId, String scope, List<String> attributeKeys, PluginCallback<Void> callback);
void loadAttribute(DeviceId deviceId, String attributeType, String attributeKey, PluginCallback<Optional<AttributeKvEntry>> callback); void loadAttribute(DeviceId deviceId, String attributeType, String attributeKey, PluginCallback<Optional<AttributeKvEntry>> callback);

View File

@ -25,6 +25,7 @@ import org.springframework.util.StringUtils;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.extensions.api.plugins.PluginCallback;
import org.thingsboard.server.extensions.api.plugins.PluginContext; import org.thingsboard.server.extensions.api.plugins.PluginContext;
import org.thingsboard.server.extensions.api.plugins.handlers.DefaultRestMsgHandler; import org.thingsboard.server.extensions.api.plugins.handlers.DefaultRestMsgHandler;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
@ -62,14 +63,14 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler {
String method = pathParams[0].toUpperCase(); String method = pathParams[0].toUpperCase();
if (DataConstants.ONEWAY.equals(method) || DataConstants.TWOWAY.equals(method)) { if (DataConstants.ONEWAY.equals(method) || DataConstants.TWOWAY.equals(method)) {
DeviceId deviceId = DeviceId.fromString(pathParams[1]); DeviceId deviceId = DeviceId.fromString(pathParams[1]);
if (!ctx.checkAccess(deviceId)) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED));
return;
}
JsonNode rpcRequestBody = jsonMapper.readTree(request.getRequestBody()); JsonNode rpcRequestBody = jsonMapper.readTree(request.getRequestBody());
RpcRequest cmd = new RpcRequest(rpcRequestBody.get("method").asText(), RpcRequest cmd = new RpcRequest(rpcRequestBody.get("method").asText(),
jsonMapper.writeValueAsString(rpcRequestBody.get("params"))); jsonMapper.writeValueAsString(rpcRequestBody.get("params")));
ctx.checkAccess(deviceId, new PluginCallback<Void>() {
@Override
public void onSuccess(PluginContext ctx, Void value) {
if (rpcRequestBody.has("timeout")) { if (rpcRequestBody.has("timeout")) {
cmd.setTimeout(rpcRequestBody.get("timeout").asLong()); cmd.setTimeout(rpcRequestBody.get("timeout").asLong());
} }
@ -83,6 +84,13 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler {
body body
); );
rpcManager.process(ctx, new LocalRequestMetaData(rpcRequest, msg.getResponseHolder())); rpcManager.process(ctx, new LocalRequestMetaData(rpcRequest, msg.getResponseHolder()));
}
@Override
public void onFailure(PluginContext ctx, Exception e) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED));
}
});
valid = true; valid = true;
} }
} }

View File

@ -164,7 +164,7 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
} }
}); });
if (attributes.size() > 0) { if (attributes.size() > 0) {
ctx.saveAttributes(deviceId, scope, attributes, new PluginCallback<Void>() { ctx.saveAttributes(ctx.getSecurityCtx().orElseThrow(() -> new IllegalArgumentException()).getTenantId(), deviceId, scope, attributes, new PluginCallback<Void>() {
@Override @Override
public void onSuccess(PluginContext ctx, Void value) { public void onSuccess(PluginContext ctx, Void value) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK)); msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK));
@ -182,8 +182,8 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
} }
} }
} }
} catch (IOException e) { } catch (IOException | RuntimeException e) {
log.debug("Failed to process POST request due to IO exception", e); log.debug("Failed to process POST request due to exception", e);
} }
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
} }
@ -202,7 +202,7 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
String keysParam = request.getParameter("keys"); String keysParam = request.getParameter("keys");
if (!StringUtils.isEmpty(keysParam)) { if (!StringUtils.isEmpty(keysParam)) {
String[] keys = keysParam.split(","); String[] keys = keysParam.split(",");
ctx.removeAttributes(deviceId, scope, Arrays.asList(keys), new PluginCallback<Void>() { ctx.removeAttributes(ctx.getSecurityCtx().orElseThrow(() -> new IllegalArgumentException()).getTenantId(), deviceId, scope, Arrays.asList(keys), new PluginCallback<Void>() {
@Override @Override
public void onSuccess(PluginContext ctx, Void value) { public void onSuccess(PluginContext ctx, Void value) {
msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK)); msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.OK));

View File

@ -120,7 +120,7 @@ public class TelemetryRuleMsgHandler extends DefaultRuleMsgHandler {
@Override @Override
public void handleUpdateAttributesRequest(PluginContext ctx, TenantId tenantId, RuleId ruleId, UpdateAttributesRequestRuleToPluginMsg msg) { public void handleUpdateAttributesRequest(PluginContext ctx, TenantId tenantId, RuleId ruleId, UpdateAttributesRequestRuleToPluginMsg msg) {
UpdateAttributesRequest request = msg.getPayload(); UpdateAttributesRequest request = msg.getPayload();
ctx.saveAttributes(msg.getDeviceId(), DataConstants.CLIENT_SCOPE, request.getAttributes().stream().collect(Collectors.toList()), ctx.saveAttributes(msg.getTenantId(), msg.getDeviceId(), DataConstants.CLIENT_SCOPE, request.getAttributes().stream().collect(Collectors.toList()),
new PluginCallback<Void>() { new PluginCallback<Void>() {
@Override @Override
public void onSuccess(PluginContext ctx, Void value) { public void onSuccess(PluginContext ctx, Void value) {

View File

@ -21,6 +21,7 @@ import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.kv.*; import org.thingsboard.server.common.data.kv.*;
import org.thingsboard.server.extensions.api.exception.UnauthorizedException;
import org.thingsboard.server.extensions.api.plugins.PluginCallback; import org.thingsboard.server.extensions.api.plugins.PluginCallback;
import org.thingsboard.server.extensions.api.plugins.PluginContext; import org.thingsboard.server.extensions.api.plugins.PluginContext;
import org.thingsboard.server.extensions.api.plugins.handlers.DefaultWebsocketMsgHandler; import org.thingsboard.server.extensions.api.plugins.handlers.DefaultWebsocketMsgHandler;
@ -122,8 +123,14 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
@Override @Override
public void onFailure(PluginContext ctx, Exception e) { public void onFailure(PluginContext ctx, Exception e) {
log.error("Failed to fetch attributes!", e); log.error("Failed to fetch attributes!", e);
SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, 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 attributes!"); "Failed to fetch attributes!");
}
sendWsMsg(ctx, sessionRef, update); sendWsMsg(ctx, sessionRef, update);
} }
}; };
@ -207,8 +214,14 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
@Override @Override
public void onFailure(PluginContext ctx, Exception e) { public void onFailure(PluginContext ctx, Exception e) {
SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, 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!"); "Failed to fetch data!");
}
sendWsMsg(ctx, sessionRef, update); sendWsMsg(ctx, sessionRef, update);
} }
}); });
@ -263,12 +276,6 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
return; return;
} }
DeviceId deviceId = DeviceId.fromString(cmd.getDeviceId()); DeviceId deviceId = DeviceId.fromString(cmd.getDeviceId());
if (!ctx.checkAccess(deviceId)) {
SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.UNAUTHORIZED,
SubscriptionErrorCode.UNAUTHORIZED.getDefaultMsg());
sendWsMsg(ctx, sessionRef, update);
return;
}
List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet())); 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.getLimit(), Aggregation.valueOf(cmd.getAgg()))).collect(Collectors.toList()); List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getLimit(), Aggregation.valueOf(cmd.getAgg()))).collect(Collectors.toList());
ctx.loadTimeseries(deviceId, queries, new PluginCallback<List<TsKvEntry>>() { ctx.loadTimeseries(deviceId, queries, new PluginCallback<List<TsKvEntry>>() {
@ -279,8 +286,15 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
@Override @Override
public void onFailure(PluginContext ctx, Exception e) { public void onFailure(PluginContext ctx, Exception e) {
sendWsMsg(ctx, sessionRef, new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR, SubscriptionUpdate update;
"Failed to fetch data!")); 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(ctx, sessionRef, update);
} }
}); });
} }
@ -313,13 +327,6 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
sendWsMsg(ctx, sessionRef, update); sendWsMsg(ctx, sessionRef, update);
return false; return false;
} }
DeviceId deviceId = DeviceId.fromString(cmd.getDeviceId());
if (!ctx.checkAccess(deviceId)) {
SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.UNAUTHORIZED,
SubscriptionErrorCode.UNAUTHORIZED.getDefaultMsg());
sendWsMsg(ctx, sessionRef, update);
return false;
}
return true; return true;
} }