693 changed files with 31498 additions and 13495 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,81 @@ |
|||||
|
-- |
||||
|
-- Copyright © 2016-2018 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. |
||||
|
-- |
||||
|
|
||||
|
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_name; |
||||
|
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_search_text; |
||||
|
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_customer; |
||||
|
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_entity_id; |
||||
|
|
||||
|
DROP TABLE IF EXISTS thingsboard.entity_views; |
||||
|
|
||||
|
CREATE TABLE IF NOT EXISTS thingsboard.entity_views ( |
||||
|
id timeuuid, |
||||
|
entity_id timeuuid, |
||||
|
entity_type text, |
||||
|
tenant_id timeuuid, |
||||
|
customer_id timeuuid, |
||||
|
name text, |
||||
|
keys text, |
||||
|
start_ts bigint, |
||||
|
end_ts bigint, |
||||
|
search_text text, |
||||
|
additional_info text, |
||||
|
PRIMARY KEY (id, entity_id, tenant_id, customer_id) |
||||
|
); |
||||
|
|
||||
|
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_name AS |
||||
|
SELECT * |
||||
|
from thingsboard.entity_views |
||||
|
WHERE tenant_id IS NOT NULL |
||||
|
AND entity_id IS NOT NULL |
||||
|
AND customer_id IS NOT NULL |
||||
|
AND name IS NOT NULL |
||||
|
AND id IS NOT NULL |
||||
|
PRIMARY KEY (tenant_id, name, id, customer_id, entity_id) |
||||
|
WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC); |
||||
|
|
||||
|
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS |
||||
|
SELECT * |
||||
|
from thingsboard.entity_views |
||||
|
WHERE tenant_id IS NOT NULL |
||||
|
AND entity_id IS NOT NULL |
||||
|
AND customer_id IS NOT NULL |
||||
|
AND search_text IS NOT NULL |
||||
|
AND id IS NOT NULL |
||||
|
PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id) |
||||
|
WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC); |
||||
|
|
||||
|
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer AS |
||||
|
SELECT * |
||||
|
from thingsboard.entity_views |
||||
|
WHERE tenant_id IS NOT NULL |
||||
|
AND customer_id IS NOT NULL |
||||
|
AND entity_id IS NOT NULL |
||||
|
AND search_text IS NOT NULL |
||||
|
AND id IS NOT NULL |
||||
|
PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id) |
||||
|
WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC); |
||||
|
|
||||
|
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity_id AS |
||||
|
SELECT * |
||||
|
from thingsboard.entity_views |
||||
|
WHERE tenant_id IS NOT NULL |
||||
|
AND customer_id IS NOT NULL |
||||
|
AND entity_id IS NOT NULL |
||||
|
AND search_text IS NOT NULL |
||||
|
AND id IS NOT NULL |
||||
|
PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id) |
||||
|
WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC); |
||||
@ -0,0 +1,31 @@ |
|||||
|
-- |
||||
|
-- Copyright © 2016-2018 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. |
||||
|
-- |
||||
|
|
||||
|
DROP TABLE IF EXISTS entity_views; |
||||
|
|
||||
|
CREATE TABLE IF NOT EXISTS entity_views ( |
||||
|
id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY, |
||||
|
entity_id varchar(31), |
||||
|
entity_type varchar(255), |
||||
|
tenant_id varchar(31), |
||||
|
customer_id varchar(31), |
||||
|
name varchar(255), |
||||
|
keys varchar(255), |
||||
|
start_ts bigint, |
||||
|
end_ts bigint, |
||||
|
search_text varchar(255), |
||||
|
additional_info varchar |
||||
|
); |
||||
@ -0,0 +1,110 @@ |
|||||
|
-- |
||||
|
-- Copyright © 2016-2018 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. |
||||
|
-- |
||||
|
|
||||
|
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_name; |
||||
|
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_search_text; |
||||
|
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_customer; |
||||
|
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_entity_id; |
||||
|
|
||||
|
DROP TABLE IF EXISTS thingsboard.entity_views; |
||||
|
|
||||
|
CREATE TABLE IF NOT EXISTS thingsboard.entity_view ( |
||||
|
id timeuuid, |
||||
|
entity_id timeuuid, |
||||
|
entity_type text, |
||||
|
tenant_id timeuuid, |
||||
|
customer_id timeuuid, |
||||
|
name text, |
||||
|
type text, |
||||
|
keys text, |
||||
|
start_ts bigint, |
||||
|
end_ts bigint, |
||||
|
search_text text, |
||||
|
additional_info text, |
||||
|
PRIMARY KEY (id, entity_id, tenant_id, customer_id, type) |
||||
|
); |
||||
|
|
||||
|
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_name AS |
||||
|
SELECT * |
||||
|
from thingsboard.entity_view |
||||
|
WHERE tenant_id IS NOT NULL |
||||
|
AND entity_id IS NOT NULL |
||||
|
AND customer_id IS NOT NULL |
||||
|
AND type IS NOT NULL |
||||
|
AND name IS NOT NULL |
||||
|
AND id IS NOT NULL |
||||
|
PRIMARY KEY (tenant_id, name, id, customer_id, entity_id, type) |
||||
|
WITH CLUSTERING ORDER BY (name ASC, id DESC, customer_id DESC); |
||||
|
|
||||
|
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_search_text AS |
||||
|
SELECT * |
||||
|
from thingsboard.entity_view |
||||
|
WHERE tenant_id IS NOT NULL |
||||
|
AND entity_id IS NOT NULL |
||||
|
AND customer_id IS NOT NULL |
||||
|
AND type IS NOT NULL |
||||
|
AND search_text IS NOT NULL |
||||
|
AND id IS NOT NULL |
||||
|
PRIMARY KEY (tenant_id, search_text, id, customer_id, entity_id, type) |
||||
|
WITH CLUSTERING ORDER BY (search_text ASC, id DESC, customer_id DESC); |
||||
|
|
||||
|
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_by_type_and_search_text AS |
||||
|
SELECT * |
||||
|
from thingsboard.entity_view |
||||
|
WHERE tenant_id IS NOT NULL |
||||
|
AND entity_id IS NOT NULL |
||||
|
AND customer_id IS NOT NULL |
||||
|
AND type IS NOT NULL |
||||
|
AND search_text IS NOT NULL |
||||
|
AND id IS NOT NULL |
||||
|
PRIMARY KEY (tenant_id, type, search_text, id, customer_id, entity_id) |
||||
|
WITH CLUSTERING ORDER BY (type ASC, search_text ASC, id DESC, customer_id DESC); |
||||
|
|
||||
|
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer AS |
||||
|
SELECT * |
||||
|
from thingsboard.entity_view |
||||
|
WHERE tenant_id IS NOT NULL |
||||
|
AND customer_id IS NOT NULL |
||||
|
AND entity_id IS NOT NULL |
||||
|
AND type IS NOT NULL |
||||
|
AND search_text IS NOT NULL |
||||
|
AND id IS NOT NULL |
||||
|
PRIMARY KEY (tenant_id, customer_id, search_text, id, entity_id, type) |
||||
|
WITH CLUSTERING ORDER BY (customer_id DESC, search_text ASC, id DESC); |
||||
|
|
||||
|
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_customer_and_type AS |
||||
|
SELECT * |
||||
|
from thingsboard.entity_view |
||||
|
WHERE tenant_id IS NOT NULL |
||||
|
AND customer_id IS NOT NULL |
||||
|
AND entity_id IS NOT NULL |
||||
|
AND type IS NOT NULL |
||||
|
AND search_text IS NOT NULL |
||||
|
AND id IS NOT NULL |
||||
|
PRIMARY KEY (tenant_id, type, customer_id, search_text, id, entity_id) |
||||
|
WITH CLUSTERING ORDER BY (type ASC, customer_id DESC, search_text ASC, id DESC); |
||||
|
|
||||
|
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_entity_id AS |
||||
|
SELECT * |
||||
|
from thingsboard.entity_view |
||||
|
WHERE tenant_id IS NOT NULL |
||||
|
AND customer_id IS NOT NULL |
||||
|
AND entity_id IS NOT NULL |
||||
|
AND type IS NOT NULL |
||||
|
AND search_text IS NOT NULL |
||||
|
AND id IS NOT NULL |
||||
|
PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id, type) |
||||
|
WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC); |
||||
@ -0,0 +1,32 @@ |
|||||
|
-- |
||||
|
-- Copyright © 2016-2018 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. |
||||
|
-- |
||||
|
|
||||
|
DROP TABLE IF EXISTS entity_views; |
||||
|
|
||||
|
CREATE TABLE IF NOT EXISTS entity_view ( |
||||
|
id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY, |
||||
|
entity_id varchar(31), |
||||
|
entity_type varchar(255), |
||||
|
tenant_id varchar(31), |
||||
|
customer_id varchar(31), |
||||
|
type varchar(255), |
||||
|
name varchar(255), |
||||
|
keys varchar(255), |
||||
|
start_ts bigint, |
||||
|
end_ts bigint, |
||||
|
search_text varchar(255), |
||||
|
additional_info varchar |
||||
|
); |
||||
@ -0,0 +1,17 @@ |
|||||
|
-- |
||||
|
-- Copyright © 2016-2018 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. |
||||
|
-- |
||||
|
|
||||
|
ALTER TABLE component_descriptor ADD UNIQUE (clazz); |
||||
@ -0,0 +1,39 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 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.device; |
||||
|
|
||||
|
import org.thingsboard.server.actors.ActorSystemContext; |
||||
|
import org.thingsboard.server.actors.service.ContextBasedCreator; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
|
||||
|
public class DeviceActorCreator extends ContextBasedCreator<DeviceActor> { |
||||
|
private static final long serialVersionUID = 1L; |
||||
|
|
||||
|
private final TenantId tenantId; |
||||
|
private final DeviceId deviceId; |
||||
|
|
||||
|
public DeviceActorCreator(ActorSystemContext context, TenantId tenantId, DeviceId deviceId) { |
||||
|
super(context); |
||||
|
this.tenantId = tenantId; |
||||
|
this.deviceId = deviceId; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public DeviceActor create() { |
||||
|
return new DeviceActor(context, tenantId, deviceId); |
||||
|
} |
||||
|
} |
||||
@ -1,40 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 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.device; |
|
||||
|
|
||||
import lombok.AllArgsConstructor; |
|
||||
import lombok.Data; |
|
||||
import org.thingsboard.server.common.data.id.SessionId; |
|
||||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|
||||
import org.thingsboard.server.common.msg.session.SessionMsgType; |
|
||||
|
|
||||
import java.util.Optional; |
|
||||
|
|
||||
/** |
|
||||
* Created by ashvayka on 17.04.18. |
|
||||
*/ |
|
||||
@Data |
|
||||
@AllArgsConstructor |
|
||||
public final class PendingSessionMsgData { |
|
||||
|
|
||||
private final SessionId sessionId; |
|
||||
private final Optional<ServerAddress> serverAddress; |
|
||||
private final SessionMsgType sessionMsgType; |
|
||||
private final int requestId; |
|
||||
private final boolean replyOnQueueAck; |
|
||||
private int ackMsgCount; |
|
||||
|
|
||||
} |
|
||||
@ -1,156 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 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.session; |
|
||||
|
|
||||
import akka.actor.ActorContext; |
|
||||
import akka.event.LoggingAdapter; |
|
||||
import org.thingsboard.server.actors.ActorSystemContext; |
|
||||
import org.thingsboard.server.actors.shared.SessionTimeoutMsg; |
|
||||
import org.thingsboard.server.common.data.id.SessionId; |
|
||||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|
||||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|
||||
import org.thingsboard.server.common.msg.core.AttributesSubscribeMsg; |
|
||||
import org.thingsboard.server.common.msg.core.ResponseMsg; |
|
||||
import org.thingsboard.server.common.msg.core.RpcSubscribeMsg; |
|
||||
import org.thingsboard.server.common.msg.core.SessionCloseMsg; |
|
||||
import org.thingsboard.server.common.msg.core.SessionOpenMsg; |
|
||||
import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; |
|
||||
import org.thingsboard.server.common.msg.session.BasicSessionActorToAdaptorMsg; |
|
||||
import org.thingsboard.server.common.msg.session.FromDeviceMsg; |
|
||||
import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; |
|
||||
import org.thingsboard.server.common.msg.session.SessionMsgType; |
|
||||
import org.thingsboard.server.common.msg.session.SessionType; |
|
||||
import org.thingsboard.server.common.msg.session.ToDeviceMsg; |
|
||||
import org.thingsboard.server.common.msg.session.TransportToDeviceSessionActorMsg; |
|
||||
import org.thingsboard.server.common.msg.session.ex.SessionException; |
|
||||
|
|
||||
import java.util.HashMap; |
|
||||
import java.util.Map; |
|
||||
import java.util.Optional; |
|
||||
|
|
||||
class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor { |
|
||||
|
|
||||
private boolean firstMsg = true; |
|
||||
private Map<Integer, DeviceToDeviceActorMsg> pendingMap = new HashMap<>(); |
|
||||
private Optional<ServerAddress> currentTargetServer; |
|
||||
private boolean subscribedToAttributeUpdates; |
|
||||
private boolean subscribedToRpcCommands; |
|
||||
|
|
||||
public ASyncMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) { |
|
||||
super(ctx, logger, sessionId); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void processToDeviceActorMsg(ActorContext ctx, TransportToDeviceSessionActorMsg msg) { |
|
||||
updateSessionCtx(msg, SessionType.ASYNC); |
|
||||
DeviceToDeviceActorMsg pendingMsg = toDeviceMsg(msg); |
|
||||
FromDeviceMsg fromDeviceMsg = pendingMsg.getPayload(); |
|
||||
if (firstMsg) { |
|
||||
if (fromDeviceMsg.getMsgType() != SessionMsgType.SESSION_OPEN) { |
|
||||
toDeviceMsg(new SessionOpenMsg()).ifPresent(m -> forwardToAppActor(ctx, m)); |
|
||||
} |
|
||||
firstMsg = false; |
|
||||
} |
|
||||
switch (fromDeviceMsg.getMsgType()) { |
|
||||
case POST_TELEMETRY_REQUEST: |
|
||||
case POST_ATTRIBUTES_REQUEST: |
|
||||
FromDeviceRequestMsg requestMsg = (FromDeviceRequestMsg) fromDeviceMsg; |
|
||||
if (requestMsg.getRequestId() >= 0) { |
|
||||
logger.debug("[{}] Pending request {} registered", requestMsg.getRequestId(), requestMsg.getMsgType()); |
|
||||
//TODO: handle duplicates.
|
|
||||
pendingMap.put(requestMsg.getRequestId(), pendingMsg); |
|
||||
} |
|
||||
break; |
|
||||
case SUBSCRIBE_ATTRIBUTES_REQUEST: |
|
||||
subscribedToAttributeUpdates = true; |
|
||||
break; |
|
||||
case UNSUBSCRIBE_ATTRIBUTES_REQUEST: |
|
||||
subscribedToAttributeUpdates = false; |
|
||||
break; |
|
||||
case SUBSCRIBE_RPC_COMMANDS_REQUEST: |
|
||||
subscribedToRpcCommands = true; |
|
||||
break; |
|
||||
case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: |
|
||||
subscribedToRpcCommands = false; |
|
||||
break; |
|
||||
default: |
|
||||
break; |
|
||||
} |
|
||||
currentTargetServer = forwardToAppActor(ctx, pendingMsg); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void processToDeviceMsg(ActorContext context, ToDeviceMsg msg) { |
|
||||
try { |
|
||||
if (msg.getSessionMsgType() != SessionMsgType.SESSION_CLOSE) { |
|
||||
switch (msg.getSessionMsgType()) { |
|
||||
case STATUS_CODE_RESPONSE: |
|
||||
case GET_ATTRIBUTES_RESPONSE: |
|
||||
ResponseMsg responseMsg = (ResponseMsg) msg; |
|
||||
if (responseMsg.getRequestId() >= 0) { |
|
||||
logger.debug("[{}] Pending request processed: {}", responseMsg.getRequestId(), responseMsg); |
|
||||
pendingMap.remove(responseMsg.getRequestId()); |
|
||||
} |
|
||||
break; |
|
||||
default: |
|
||||
break; |
|
||||
} |
|
||||
sessionCtx.onMsg(new BasicSessionActorToAdaptorMsg(this.sessionCtx, msg)); |
|
||||
} else { |
|
||||
sessionCtx.onMsg(org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg.onCredentialsRevoked(sessionCtx.getSessionId())); |
|
||||
} |
|
||||
} catch (SessionException e) { |
|
||||
logger.warning("Failed to push session response msg", e); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg) { |
|
||||
// TODO Auto-generated method stub
|
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void cleanupSession(ActorContext ctx) { |
|
||||
toDeviceMsg(new SessionCloseMsg()).ifPresent(m -> forwardToAppActor(ctx, m)); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void processClusterEvent(ActorContext context, ClusterEventMsg msg) { |
|
||||
if (pendingMap.size() > 0 || subscribedToAttributeUpdates || subscribedToRpcCommands) { |
|
||||
Optional<ServerAddress> newTargetServer = systemContext.getRoutingService().resolveById(getDeviceId()); |
|
||||
if (!newTargetServer.equals(currentTargetServer)) { |
|
||||
firstMsg = true; |
|
||||
currentTargetServer = newTargetServer; |
|
||||
pendingMap.values().forEach(v -> { |
|
||||
forwardToAppActor(context, v, currentTargetServer); |
|
||||
if (currentTargetServer.isPresent()) { |
|
||||
logger.debug("[{}] Forwarded msg to new server: {}", sessionId, currentTargetServer.get()); |
|
||||
} else { |
|
||||
logger.debug("[{}] Forwarded msg to local server.", sessionId); |
|
||||
} |
|
||||
}); |
|
||||
if (subscribedToAttributeUpdates) { |
|
||||
toDeviceMsg(new AttributesSubscribeMsg()).ifPresent(m -> forwardToAppActor(context, m, currentTargetServer)); |
|
||||
logger.debug("[{}] Forwarded attributes subscription.", sessionId); |
|
||||
} |
|
||||
if (subscribedToRpcCommands) { |
|
||||
toDeviceMsg(new RpcSubscribeMsg()).ifPresent(m -> forwardToAppActor(context, m, currentTargetServer)); |
|
||||
logger.debug("[{}] Forwarded rpc commands subscription.", sessionId); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,122 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 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.session; |
|
||||
|
|
||||
import akka.actor.ActorContext; |
|
||||
import akka.actor.ActorRef; |
|
||||
import akka.event.LoggingAdapter; |
|
||||
import org.thingsboard.server.actors.ActorSystemContext; |
|
||||
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; |
|
||||
import org.thingsboard.server.actors.shared.SessionTimeoutMsg; |
|
||||
import org.thingsboard.server.common.data.id.DeviceId; |
|
||||
import org.thingsboard.server.common.data.id.SessionId; |
|
||||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|
||||
import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; |
|
||||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|
||||
import org.thingsboard.server.common.msg.device.BasicDeviceToDeviceActorMsg; |
|
||||
import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; |
|
||||
import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg; |
|
||||
import org.thingsboard.server.common.msg.session.FromDeviceMsg; |
|
||||
import org.thingsboard.server.common.msg.session.SessionContext; |
|
||||
import org.thingsboard.server.common.msg.session.SessionCtrlMsg; |
|
||||
import org.thingsboard.server.common.msg.session.SessionType; |
|
||||
import org.thingsboard.server.common.msg.session.ToDeviceMsg; |
|
||||
import org.thingsboard.server.common.msg.session.TransportToDeviceSessionActorMsg; |
|
||||
import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; |
|
||||
|
|
||||
import java.util.Optional; |
|
||||
|
|
||||
abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgProcessor { |
|
||||
|
|
||||
protected final SessionId sessionId; |
|
||||
protected SessionContext sessionCtx; |
|
||||
protected DeviceToDeviceActorMsg deviceToDeviceActorMsgPrototype; |
|
||||
|
|
||||
protected AbstractSessionActorMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) { |
|
||||
super(ctx, logger); |
|
||||
this.sessionId = sessionId; |
|
||||
} |
|
||||
|
|
||||
protected abstract void processToDeviceActorMsg(ActorContext ctx, TransportToDeviceSessionActorMsg msg); |
|
||||
|
|
||||
protected abstract void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg); |
|
||||
|
|
||||
protected abstract void processToDeviceMsg(ActorContext context, ToDeviceMsg msg); |
|
||||
|
|
||||
public abstract void processClusterEvent(ActorContext context, ClusterEventMsg msg); |
|
||||
|
|
||||
protected void processSessionCtrlMsg(ActorContext ctx, SessionCtrlMsg msg) { |
|
||||
if (msg instanceof SessionCloseMsg) { |
|
||||
cleanupSession(ctx); |
|
||||
terminateSession(ctx, sessionId); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected void cleanupSession(ActorContext ctx) { |
|
||||
} |
|
||||
|
|
||||
protected void updateSessionCtx(TransportToDeviceSessionActorMsg msg, SessionType type) { |
|
||||
sessionCtx = msg.getSessionMsg().getSessionContext(); |
|
||||
deviceToDeviceActorMsgPrototype = new BasicDeviceToDeviceActorMsg(msg, type); |
|
||||
} |
|
||||
|
|
||||
protected DeviceToDeviceActorMsg toDeviceMsg(TransportToDeviceSessionActorMsg msg) { |
|
||||
AdaptorToSessionActorMsg adaptorMsg = msg.getSessionMsg(); |
|
||||
return new BasicDeviceToDeviceActorMsg(deviceToDeviceActorMsgPrototype, adaptorMsg.getMsg()); |
|
||||
} |
|
||||
|
|
||||
protected Optional<DeviceToDeviceActorMsg> toDeviceMsg(FromDeviceMsg msg) { |
|
||||
if (deviceToDeviceActorMsgPrototype != null) { |
|
||||
return Optional.of(new BasicDeviceToDeviceActorMsg(deviceToDeviceActorMsgPrototype, msg)); |
|
||||
} else { |
|
||||
return Optional.empty(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected Optional<ServerAddress> forwardToAppActor(ActorContext ctx, DeviceToDeviceActorMsg toForward) { |
|
||||
Optional<ServerAddress> address = systemContext.getRoutingService().resolveById(toForward.getDeviceId()); |
|
||||
forwardToAppActor(ctx, toForward, address); |
|
||||
return address; |
|
||||
} |
|
||||
|
|
||||
protected Optional<ServerAddress> forwardToAppActorIfAddressChanged(ActorContext ctx, DeviceToDeviceActorMsg toForward, Optional<ServerAddress> oldAddress) { |
|
||||
|
|
||||
Optional<ServerAddress> newAddress = systemContext.getRoutingService().resolveById(toForward.getDeviceId()); |
|
||||
if (!newAddress.equals(oldAddress)) { |
|
||||
getAppActor().tell(new SendToClusterMsg(toForward.getDeviceId(), toForward |
|
||||
.toOtherAddress(systemContext.getRoutingService().getCurrentServer())), ctx.self()); |
|
||||
} |
|
||||
return newAddress; |
|
||||
} |
|
||||
|
|
||||
protected void forwardToAppActor(ActorContext ctx, DeviceToDeviceActorMsg toForward, Optional<ServerAddress> address) { |
|
||||
if (address.isPresent()) { |
|
||||
systemContext.getRpcService().tell(systemContext.getEncodingService().convertToProtoDataMessage(address.get(), |
|
||||
toForward.toOtherAddress(systemContext.getRoutingService().getCurrentServer()))); |
|
||||
} else { |
|
||||
getAppActor().tell(toForward, ctx.self()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static void terminateSession(ActorContext ctx, SessionId sessionId) { |
|
||||
ctx.parent().tell(new SessionTerminationMsg(sessionId), ActorRef.noSender()); |
|
||||
ctx.stop(ctx.self()); |
|
||||
} |
|
||||
|
|
||||
public DeviceId getDeviceId() { |
|
||||
return deviceToDeviceActorMsgPrototype.getDeviceId(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,143 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 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.session; |
|
||||
|
|
||||
import akka.actor.OneForOneStrategy; |
|
||||
import akka.actor.SupervisorStrategy; |
|
||||
import akka.event.Logging; |
|
||||
import akka.event.LoggingAdapter; |
|
||||
import org.thingsboard.server.actors.ActorSystemContext; |
|
||||
import org.thingsboard.server.actors.service.ContextAwareActor; |
|
||||
import org.thingsboard.server.actors.service.ContextBasedCreator; |
|
||||
import org.thingsboard.server.actors.shared.SessionTimeoutMsg; |
|
||||
import org.thingsboard.server.common.data.id.SessionId; |
|
||||
import org.thingsboard.server.common.msg.TbActorMsg; |
|
||||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|
||||
import org.thingsboard.server.common.msg.core.ActorSystemToDeviceSessionActorMsg; |
|
||||
import org.thingsboard.server.common.msg.session.SessionCtrlMsg; |
|
||||
import org.thingsboard.server.common.msg.session.SessionMsg; |
|
||||
import org.thingsboard.server.common.msg.session.SessionType; |
|
||||
import org.thingsboard.server.common.msg.session.TransportToDeviceSessionActorMsg; |
|
||||
import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; |
|
||||
import scala.concurrent.duration.Duration; |
|
||||
|
|
||||
public class SessionActor extends ContextAwareActor { |
|
||||
|
|
||||
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); |
|
||||
|
|
||||
private final SessionId sessionId; |
|
||||
private AbstractSessionActorMsgProcessor processor; |
|
||||
|
|
||||
private SessionActor(ActorSystemContext systemContext, SessionId sessionId) { |
|
||||
super(systemContext); |
|
||||
this.sessionId = sessionId; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public SupervisorStrategy supervisorStrategy() { |
|
||||
return new OneForOneStrategy(-1, Duration.Inf(), |
|
||||
throwable -> { |
|
||||
logger.error(throwable, "Unknown session error"); |
|
||||
if (throwable instanceof Error) { |
|
||||
return OneForOneStrategy.escalate(); |
|
||||
} else { |
|
||||
return OneForOneStrategy.resume(); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected boolean process(TbActorMsg msg) { |
|
||||
switch (msg.getMsgType()) { |
|
||||
case TRANSPORT_TO_DEVICE_SESSION_ACTOR_MSG: |
|
||||
processTransportToSessionMsg((TransportToDeviceSessionActorMsg) msg); |
|
||||
break; |
|
||||
case ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG: |
|
||||
processActorsToSessionMsg((ActorSystemToDeviceSessionActorMsg) msg); |
|
||||
break; |
|
||||
case SESSION_TIMEOUT_MSG: |
|
||||
processTimeoutMsg((SessionTimeoutMsg) msg); |
|
||||
break; |
|
||||
case SESSION_CTRL_MSG: |
|
||||
processSessionCloseMsg((SessionCtrlMsg) msg); |
|
||||
break; |
|
||||
case CLUSTER_EVENT_MSG: |
|
||||
processClusterEvent((ClusterEventMsg) msg); |
|
||||
break; |
|
||||
default: return false; |
|
||||
} |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
private void processClusterEvent(ClusterEventMsg msg) { |
|
||||
processor.processClusterEvent(context(), msg); |
|
||||
} |
|
||||
|
|
||||
private void processTransportToSessionMsg(TransportToDeviceSessionActorMsg msg) { |
|
||||
initProcessor(msg); |
|
||||
processor.processToDeviceActorMsg(context(), msg); |
|
||||
} |
|
||||
|
|
||||
private void processActorsToSessionMsg(ActorSystemToDeviceSessionActorMsg msg) { |
|
||||
processor.processToDeviceMsg(context(), msg.getMsg()); |
|
||||
} |
|
||||
|
|
||||
private void processTimeoutMsg(SessionTimeoutMsg msg) { |
|
||||
if (processor != null) { |
|
||||
processor.processTimeoutMsg(context(), msg); |
|
||||
} else { |
|
||||
logger.warning("[{}] Can't process timeout msg: {} without processor", sessionId, msg); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void processSessionCloseMsg(SessionCtrlMsg msg) { |
|
||||
if (processor != null) { |
|
||||
processor.processSessionCtrlMsg(context(), msg); |
|
||||
} else if (msg instanceof SessionCloseMsg) { |
|
||||
AbstractSessionActorMsgProcessor.terminateSession(context(), sessionId); |
|
||||
} else { |
|
||||
logger.warning("[{}] Can't process session ctrl msg: {} without processor", sessionId, msg); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void initProcessor(TransportToDeviceSessionActorMsg msg) { |
|
||||
if (processor == null) { |
|
||||
SessionMsg sessionMsg = (SessionMsg) msg.getSessionMsg(); |
|
||||
if (sessionMsg.getSessionContext().getSessionType() == SessionType.SYNC) { |
|
||||
processor = new SyncMsgProcessor(systemContext, logger, sessionId); |
|
||||
} else { |
|
||||
processor = new ASyncMsgProcessor(systemContext, logger, sessionId); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static class ActorCreator extends ContextBasedCreator<SessionActor> { |
|
||||
private static final long serialVersionUID = 1L; |
|
||||
|
|
||||
private final SessionId sessionId; |
|
||||
|
|
||||
public ActorCreator(ActorSystemContext context, SessionId sessionId) { |
|
||||
super(context); |
|
||||
this.sessionId = sessionId; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public SessionActor create() throws Exception { |
|
||||
return new SessionActor(context, sessionId); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,180 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 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.session; |
|
||||
|
|
||||
import akka.actor.ActorInitializationException; |
|
||||
import akka.actor.ActorRef; |
|
||||
import akka.actor.InvalidActorNameException; |
|
||||
import akka.actor.LocalActorRef; |
|
||||
import akka.actor.OneForOneStrategy; |
|
||||
import akka.actor.Props; |
|
||||
import akka.actor.SupervisorStrategy; |
|
||||
import akka.actor.Terminated; |
|
||||
import akka.event.Logging; |
|
||||
import akka.event.LoggingAdapter; |
|
||||
import akka.japi.Function; |
|
||||
import org.thingsboard.server.actors.ActorSystemContext; |
|
||||
import org.thingsboard.server.actors.service.ContextAwareActor; |
|
||||
import org.thingsboard.server.actors.service.ContextBasedCreator; |
|
||||
import org.thingsboard.server.actors.service.DefaultActorService; |
|
||||
import org.thingsboard.server.actors.shared.SessionTimeoutMsg; |
|
||||
import org.thingsboard.server.common.data.id.SessionId; |
|
||||
import org.thingsboard.server.common.msg.TbActorMsg; |
|
||||
import org.thingsboard.server.common.msg.aware.SessionAwareMsg; |
|
||||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|
||||
import org.thingsboard.server.common.msg.core.ActorSystemToDeviceSessionActorMsg; |
|
||||
import org.thingsboard.server.common.msg.core.SessionCloseMsg; |
|
||||
import org.thingsboard.server.common.msg.session.SessionCtrlMsg; |
|
||||
import scala.concurrent.duration.Duration; |
|
||||
|
|
||||
import java.util.HashMap; |
|
||||
import java.util.Map; |
|
||||
|
|
||||
public class SessionManagerActor extends ContextAwareActor { |
|
||||
|
|
||||
private static final int INITIAL_SESSION_MAP_SIZE = 1024; |
|
||||
|
|
||||
private final LoggingAdapter log = Logging.getLogger(getContext().system(), this); |
|
||||
|
|
||||
private final Map<String, ActorRef> sessionActors; |
|
||||
|
|
||||
SessionManagerActor(ActorSystemContext systemContext) { |
|
||||
super(systemContext); |
|
||||
this.sessionActors = new HashMap<>(INITIAL_SESSION_MAP_SIZE); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public SupervisorStrategy supervisorStrategy() { |
|
||||
return strategy; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected boolean process(TbActorMsg msg) { |
|
||||
//TODO Move everything here, to work with TbActorMsg
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onReceive(Object msg) throws Exception { |
|
||||
if (msg instanceof SessionCtrlMsg) { |
|
||||
onSessionCtrlMsg((SessionCtrlMsg) msg); |
|
||||
} else if (msg instanceof SessionAwareMsg) { |
|
||||
forwardToSessionActor((SessionAwareMsg) msg); |
|
||||
} else if (msg instanceof SessionTerminationMsg) { |
|
||||
onSessionTermination((SessionTerminationMsg) msg); |
|
||||
} else if (msg instanceof Terminated) { |
|
||||
onTermination((Terminated) msg); |
|
||||
} else if (msg instanceof SessionTimeoutMsg) { |
|
||||
onSessionTimeout((SessionTimeoutMsg) msg); |
|
||||
} else if (msg instanceof ClusterEventMsg) { |
|
||||
broadcast(msg); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void broadcast(Object msg) { |
|
||||
sessionActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); |
|
||||
} |
|
||||
|
|
||||
private void onSessionTimeout(SessionTimeoutMsg msg) { |
|
||||
String sessionIdStr = msg.getSessionId().toUidStr(); |
|
||||
ActorRef sessionActor = sessionActors.get(sessionIdStr); |
|
||||
if (sessionActor != null) { |
|
||||
sessionActor.tell(msg, ActorRef.noSender()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void onSessionCtrlMsg(SessionCtrlMsg msg) { |
|
||||
String sessionIdStr = msg.getSessionId().toUidStr(); |
|
||||
ActorRef sessionActor = sessionActors.get(sessionIdStr); |
|
||||
if (sessionActor != null) { |
|
||||
sessionActor.tell(msg, ActorRef.noSender()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void onSessionTermination(SessionTerminationMsg msg) { |
|
||||
String sessionIdStr = msg.getId().toUidStr(); |
|
||||
ActorRef sessionActor = sessionActors.remove(sessionIdStr); |
|
||||
if (sessionActor != null) { |
|
||||
log.debug("[{}] Removed session actor.", sessionIdStr); |
|
||||
//TODO: onSubscriptionUpdate device actor about session close;
|
|
||||
} else { |
|
||||
log.debug("[{}] Session actor was already removed.", sessionIdStr); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void forwardToSessionActor(SessionAwareMsg msg) { |
|
||||
if (msg instanceof ActorSystemToDeviceSessionActorMsg || msg instanceof SessionCloseMsg) { |
|
||||
String sessionIdStr = msg.getSessionId().toUidStr(); |
|
||||
ActorRef sessionActor = sessionActors.get(sessionIdStr); |
|
||||
if (sessionActor != null) { |
|
||||
sessionActor.tell(msg, ActorRef.noSender()); |
|
||||
} else { |
|
||||
log.debug("[{}] Session actor was already removed.", sessionIdStr); |
|
||||
} |
|
||||
} else { |
|
||||
try { |
|
||||
getOrCreateSessionActor(msg.getSessionId()).tell(msg, self()); |
|
||||
} catch (InvalidActorNameException e) { |
|
||||
log.info("Invalid msg : {}", msg); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private ActorRef getOrCreateSessionActor(SessionId sessionId) { |
|
||||
String sessionIdStr = sessionId.toUidStr(); |
|
||||
ActorRef sessionActor = sessionActors.get(sessionIdStr); |
|
||||
if (sessionActor == null) { |
|
||||
log.debug("[{}] Creating session actor.", sessionIdStr); |
|
||||
sessionActor = context().actorOf( |
|
||||
Props.create(new SessionActor.ActorCreator(systemContext, sessionId)).withDispatcher(DefaultActorService.SESSION_DISPATCHER_NAME), |
|
||||
sessionIdStr); |
|
||||
sessionActors.put(sessionIdStr, sessionActor); |
|
||||
log.debug("[{}] Created session actor.", sessionIdStr); |
|
||||
} |
|
||||
return sessionActor; |
|
||||
} |
|
||||
|
|
||||
private void onTermination(Terminated message) { |
|
||||
ActorRef terminated = message.actor(); |
|
||||
if (terminated instanceof LocalActorRef) { |
|
||||
log.info("Removed actor: {}.", terminated); |
|
||||
//TODO: cleanup session actors map
|
|
||||
} else { |
|
||||
throw new IllegalStateException("Remote actors are not supported!"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static class ActorCreator extends ContextBasedCreator<SessionManagerActor> { |
|
||||
private static final long serialVersionUID = 1L; |
|
||||
|
|
||||
public ActorCreator(ActorSystemContext context) { |
|
||||
super(context); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public SessionManagerActor create() throws Exception { |
|
||||
return new SessionManagerActor(context); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function<Throwable, SupervisorStrategy.Directive>() { |
|
||||
@Override |
|
||||
public SupervisorStrategy.Directive apply(Throwable t) { |
|
||||
logger.error(t, "Unknown failure"); |
|
||||
return SupervisorStrategy.stop(); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
@ -1,95 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 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.session; |
|
||||
|
|
||||
import akka.actor.ActorContext; |
|
||||
import akka.event.LoggingAdapter; |
|
||||
import org.thingsboard.server.actors.ActorSystemContext; |
|
||||
import org.thingsboard.server.actors.shared.SessionTimeoutMsg; |
|
||||
import org.thingsboard.server.common.data.id.SessionId; |
|
||||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|
||||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|
||||
import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; |
|
||||
import org.thingsboard.server.common.msg.session.BasicSessionActorToAdaptorMsg; |
|
||||
import org.thingsboard.server.common.msg.session.SessionContext; |
|
||||
import org.thingsboard.server.common.msg.session.SessionType; |
|
||||
import org.thingsboard.server.common.msg.session.ToDeviceMsg; |
|
||||
import org.thingsboard.server.common.msg.session.TransportToDeviceSessionActorMsg; |
|
||||
import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; |
|
||||
import org.thingsboard.server.common.msg.session.ex.SessionException; |
|
||||
|
|
||||
import java.util.Optional; |
|
||||
|
|
||||
class SyncMsgProcessor extends AbstractSessionActorMsgProcessor { |
|
||||
private DeviceToDeviceActorMsg pendingMsg; |
|
||||
private Optional<ServerAddress> currentTargetServer; |
|
||||
private boolean pendingResponse; |
|
||||
|
|
||||
public SyncMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) { |
|
||||
super(ctx, logger, sessionId); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void processToDeviceActorMsg(ActorContext ctx, TransportToDeviceSessionActorMsg msg) { |
|
||||
updateSessionCtx(msg, SessionType.SYNC); |
|
||||
pendingMsg = toDeviceMsg(msg); |
|
||||
pendingResponse = true; |
|
||||
currentTargetServer = forwardToAppActor(ctx, pendingMsg); |
|
||||
scheduleMsgWithDelay(ctx, new SessionTimeoutMsg(sessionId), getTimeout(systemContext, msg.getSessionMsg().getSessionContext()), ctx.parent()); |
|
||||
} |
|
||||
|
|
||||
public void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg) { |
|
||||
if (pendingResponse) { |
|
||||
try { |
|
||||
sessionCtx.onMsg(SessionCloseMsg.onTimeout(sessionId)); |
|
||||
} catch (SessionException e) { |
|
||||
logger.warning("Failed to push session close msg", e); |
|
||||
} |
|
||||
terminateSession(context, this.sessionId); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public void processToDeviceMsg(ActorContext context, ToDeviceMsg msg) { |
|
||||
try { |
|
||||
sessionCtx.onMsg(new BasicSessionActorToAdaptorMsg(this.sessionCtx, msg)); |
|
||||
pendingResponse = false; |
|
||||
} catch (SessionException e) { |
|
||||
logger.warning("Failed to push session response msg", e); |
|
||||
} |
|
||||
terminateSession(context, this.sessionId); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void processClusterEvent(ActorContext context, ClusterEventMsg msg) { |
|
||||
if (pendingResponse) { |
|
||||
Optional<ServerAddress> newTargetServer = forwardToAppActorIfAddressChanged(context, pendingMsg, currentTargetServer); |
|
||||
if (logger.isDebugEnabled()) { |
|
||||
if (!newTargetServer.equals(currentTargetServer)) { |
|
||||
if (newTargetServer.isPresent()) { |
|
||||
logger.debug("[{}] Forwarded msg to new server: {}", sessionId, newTargetServer.get()); |
|
||||
} else { |
|
||||
logger.debug("[{}] Forwarded msg to local server.", sessionId); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
currentTargetServer = newTargetServer; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private long getTimeout(ActorSystemContext ctx, SessionContext sessionCtx) { |
|
||||
return sessionCtx.getTimeout() > 0 ? sessionCtx.getTimeout() : ctx.getSyncSessionTimeout(); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,336 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 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.controller; |
||||
|
|
||||
|
import com.google.common.util.concurrent.FutureCallback; |
||||
|
import com.google.common.util.concurrent.Futures; |
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.http.HttpStatus; |
||||
|
import org.springframework.security.access.prepost.PreAuthorize; |
||||
|
import org.springframework.web.bind.annotation.PathVariable; |
||||
|
import org.springframework.web.bind.annotation.RequestBody; |
||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestMethod; |
||||
|
import org.springframework.web.bind.annotation.RequestParam; |
||||
|
import org.springframework.web.bind.annotation.ResponseBody; |
||||
|
import org.springframework.web.bind.annotation.ResponseStatus; |
||||
|
import org.springframework.web.bind.annotation.RestController; |
||||
|
import org.thingsboard.server.common.data.Customer; |
||||
|
import org.thingsboard.server.common.data.DataConstants; |
||||
|
import org.thingsboard.server.common.data.EntitySubtype; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.EntityView; |
||||
|
import org.thingsboard.server.common.data.audit.ActionType; |
||||
|
import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.CustomerId; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.EntityViewId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.id.UUIDBased; |
||||
|
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
||||
|
import org.thingsboard.server.common.data.page.TextPageData; |
||||
|
import org.thingsboard.server.common.data.page.TextPageLink; |
||||
|
import org.thingsboard.server.dao.exception.IncorrectParameterException; |
||||
|
import org.thingsboard.server.dao.model.ModelConstants; |
||||
|
import org.thingsboard.server.service.security.model.SecurityUser; |
||||
|
|
||||
|
import javax.annotation.Nullable; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.Collection; |
||||
|
import java.util.List; |
||||
|
import java.util.concurrent.ExecutionException; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
import static org.thingsboard.server.controller.CustomerController.CUSTOMER_ID; |
||||
|
|
||||
|
/** |
||||
|
* Created by Victor Basanets on 8/28/2017. |
||||
|
*/ |
||||
|
@RestController |
||||
|
@RequestMapping("/api") |
||||
|
@Slf4j |
||||
|
public class EntityViewController extends BaseController { |
||||
|
|
||||
|
public static final String ENTITY_VIEW_ID = "entityViewId"; |
||||
|
|
||||
|
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
||||
|
@RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.GET) |
||||
|
@ResponseBody |
||||
|
public EntityView getEntityViewById(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { |
||||
|
checkParameter(ENTITY_VIEW_ID, strEntityViewId); |
||||
|
try { |
||||
|
return checkEntityViewId(new EntityViewId(toUUID(strEntityViewId))); |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
||||
|
@RequestMapping(value = "/entityView", method = RequestMethod.POST) |
||||
|
@ResponseBody |
||||
|
public EntityView saveEntityView(@RequestBody EntityView entityView) throws ThingsboardException { |
||||
|
try { |
||||
|
entityView.setTenantId(getCurrentUser().getTenantId()); |
||||
|
EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView)); |
||||
|
List<ListenableFuture<List<Void>>> futures = new ArrayList<>(); |
||||
|
if (savedEntityView.getKeys() != null && savedEntityView.getKeys().getAttributes() != null) { |
||||
|
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs(), getCurrentUser())); |
||||
|
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs(), getCurrentUser())); |
||||
|
futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh(), getCurrentUser())); |
||||
|
} |
||||
|
for (ListenableFuture<List<Void>> future : futures) { |
||||
|
try { |
||||
|
future.get(); |
||||
|
} catch (InterruptedException | ExecutionException e) { |
||||
|
throw new RuntimeException("Failed to copy attributes to entity view", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
logEntityAction(savedEntityView.getId(), savedEntityView, null, |
||||
|
entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); |
||||
|
return savedEntityView; |
||||
|
} catch (Exception e) { |
||||
|
logEntityAction(emptyId(EntityType.ENTITY_VIEW), entityView, null, |
||||
|
entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private ListenableFuture<List<Void>> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection<String> keys, SecurityUser user) throws ThingsboardException { |
||||
|
EntityViewId entityId = entityView.getId(); |
||||
|
if (keys != null && !keys.isEmpty()) { |
||||
|
ListenableFuture<List<AttributeKvEntry>> getAttrFuture = attributesService.find(entityView.getEntityId(), scope, keys); |
||||
|
return Futures.transform(getAttrFuture, attributeKvEntries -> { |
||||
|
List<AttributeKvEntry> attributes; |
||||
|
if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) { |
||||
|
attributes = |
||||
|
attributeKvEntries.stream() |
||||
|
.filter(attributeKvEntry -> { |
||||
|
long startTime = entityView.getStartTimeMs(); |
||||
|
long endTime = entityView.getEndTimeMs(); |
||||
|
long lastUpdateTs = attributeKvEntry.getLastUpdateTs(); |
||||
|
return startTime == 0 && endTime == 0 || |
||||
|
(endTime == 0 && startTime < lastUpdateTs) || |
||||
|
(startTime == 0 && endTime > lastUpdateTs) |
||||
|
? true : startTime < lastUpdateTs && endTime > lastUpdateTs; |
||||
|
}).collect(Collectors.toList()); |
||||
|
tsSubService.saveAndNotify(entityId, scope, attributes, new FutureCallback<Void>() { |
||||
|
@Override |
||||
|
public void onSuccess(@Nullable Void tmp) { |
||||
|
try { |
||||
|
logAttributesUpdated(user, entityId, scope, attributes, null); |
||||
|
} catch (ThingsboardException e) { |
||||
|
log.error("Failed to log attribute updates", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onFailure(Throwable t) { |
||||
|
try { |
||||
|
logAttributesUpdated(user, entityId, scope, attributes, t); |
||||
|
} catch (ThingsboardException e) { |
||||
|
log.error("Failed to log attribute updates", e); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
return null; |
||||
|
}); |
||||
|
} else { |
||||
|
return Futures.immediateFuture(null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void logAttributesUpdated(SecurityUser user, EntityId entityId, String scope, List<AttributeKvEntry> attributes, Throwable e) throws ThingsboardException { |
||||
|
logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.ATTRIBUTES_UPDATED, toException(e), |
||||
|
scope, attributes); |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
||||
|
@RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.DELETE) |
||||
|
@ResponseStatus(value = HttpStatus.OK) |
||||
|
public void deleteEntityView(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { |
||||
|
checkParameter(ENTITY_VIEW_ID, strEntityViewId); |
||||
|
try { |
||||
|
EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); |
||||
|
EntityView entityView = checkEntityViewId(entityViewId); |
||||
|
entityViewService.deleteEntityView(entityViewId); |
||||
|
logEntityAction(entityViewId, entityView, entityView.getCustomerId(), |
||||
|
ActionType.DELETED, null, strEntityViewId); |
||||
|
} catch (Exception e) { |
||||
|
logEntityAction(emptyId(EntityType.ENTITY_VIEW), |
||||
|
null, |
||||
|
null, |
||||
|
ActionType.DELETED, e, strEntityViewId); |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
||||
|
@RequestMapping(value = "/tenant/entityViews", params = {"entityViewName"}, method = RequestMethod.GET) |
||||
|
@ResponseBody |
||||
|
public EntityView getTenantEntityView( |
||||
|
@RequestParam String entityViewName) throws ThingsboardException { |
||||
|
try { |
||||
|
TenantId tenantId = getCurrentUser().getTenantId(); |
||||
|
return checkNotNull(entityViewService.findEntityViewByTenantIdAndName(tenantId, entityViewName)); |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
||||
|
@RequestMapping(value = "/customer/{customerId}/entityView/{entityViewId}", method = RequestMethod.POST) |
||||
|
@ResponseBody |
||||
|
public EntityView assignEntityViewToCustomer(@PathVariable(CUSTOMER_ID) String strCustomerId, |
||||
|
@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { |
||||
|
checkParameter(CUSTOMER_ID, strCustomerId); |
||||
|
checkParameter(ENTITY_VIEW_ID, strEntityViewId); |
||||
|
try { |
||||
|
CustomerId customerId = new CustomerId(toUUID(strCustomerId)); |
||||
|
Customer customer = checkCustomerId(customerId); |
||||
|
|
||||
|
EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); |
||||
|
checkEntityViewId(entityViewId); |
||||
|
|
||||
|
EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToCustomer(entityViewId, customerId)); |
||||
|
logEntityAction(entityViewId, savedEntityView, |
||||
|
savedEntityView.getCustomerId(), |
||||
|
ActionType.ASSIGNED_TO_CUSTOMER, null, strEntityViewId, strCustomerId, customer.getName()); |
||||
|
return savedEntityView; |
||||
|
} catch (Exception e) { |
||||
|
logEntityAction(emptyId(EntityType.ENTITY_VIEW), null, |
||||
|
null, |
||||
|
ActionType.ASSIGNED_TO_CUSTOMER, e, strEntityViewId, strCustomerId); |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
||||
|
@RequestMapping(value = "/customer/entityView/{entityViewId}", method = RequestMethod.DELETE) |
||||
|
@ResponseBody |
||||
|
public EntityView unassignEntityViewFromCustomer(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException { |
||||
|
checkParameter(ENTITY_VIEW_ID, strEntityViewId); |
||||
|
try { |
||||
|
EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); |
||||
|
EntityView entityView = checkEntityViewId(entityViewId); |
||||
|
if (entityView.getCustomerId() == null || entityView.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { |
||||
|
throw new IncorrectParameterException("Entity View isn't assigned to any customer!"); |
||||
|
} |
||||
|
Customer customer = checkCustomerId(entityView.getCustomerId()); |
||||
|
EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromCustomer(entityViewId)); |
||||
|
logEntityAction(entityViewId, entityView, |
||||
|
entityView.getCustomerId(), |
||||
|
ActionType.UNASSIGNED_FROM_CUSTOMER, null, strEntityViewId, customer.getId().toString(), customer.getName()); |
||||
|
|
||||
|
return savedEntityView; |
||||
|
} catch (Exception e) { |
||||
|
logEntityAction(emptyId(EntityType.ENTITY_VIEW), null, |
||||
|
null, |
||||
|
ActionType.UNASSIGNED_FROM_CUSTOMER, e, strEntityViewId); |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
||||
|
@RequestMapping(value = "/customer/{customerId}/entityViews", params = {"limit"}, method = RequestMethod.GET) |
||||
|
@ResponseBody |
||||
|
public TextPageData<EntityView> getCustomerEntityViews( |
||||
|
@PathVariable("customerId") String strCustomerId, |
||||
|
@RequestParam int limit, |
||||
|
@RequestParam(required = false) String type, |
||||
|
@RequestParam(required = false) String textSearch, |
||||
|
@RequestParam(required = false) String idOffset, |
||||
|
@RequestParam(required = false) String textOffset) throws ThingsboardException { |
||||
|
checkParameter("customerId", strCustomerId); |
||||
|
try { |
||||
|
TenantId tenantId = getCurrentUser().getTenantId(); |
||||
|
CustomerId customerId = new CustomerId(toUUID(strCustomerId)); |
||||
|
checkCustomerId(customerId); |
||||
|
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); |
||||
|
if (type != null && type.trim().length() > 0) { |
||||
|
return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerIdAndType(tenantId, customerId, pageLink, type)); |
||||
|
} else { |
||||
|
return checkNotNull(entityViewService.findEntityViewsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
||||
|
@RequestMapping(value = "/tenant/entityViews", params = {"limit"}, method = RequestMethod.GET) |
||||
|
@ResponseBody |
||||
|
public TextPageData<EntityView> getTenantEntityViews( |
||||
|
@RequestParam int limit, |
||||
|
@RequestParam(required = false) String type, |
||||
|
@RequestParam(required = false) String textSearch, |
||||
|
@RequestParam(required = false) String idOffset, |
||||
|
@RequestParam(required = false) String textOffset) throws ThingsboardException { |
||||
|
try { |
||||
|
TenantId tenantId = getCurrentUser().getTenantId(); |
||||
|
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); |
||||
|
|
||||
|
if (type != null && type.trim().length() > 0) { |
||||
|
return checkNotNull(entityViewService.findEntityViewByTenantIdAndType(tenantId, pageLink, type)); |
||||
|
} else { |
||||
|
return checkNotNull(entityViewService.findEntityViewByTenantId(tenantId, pageLink)); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
||||
|
@RequestMapping(value = "/entityViews", method = RequestMethod.POST) |
||||
|
@ResponseBody |
||||
|
public List<EntityView> findByQuery(@RequestBody EntityViewSearchQuery query) throws ThingsboardException { |
||||
|
checkNotNull(query); |
||||
|
checkNotNull(query.getParameters()); |
||||
|
checkNotNull(query.getEntityViewTypes()); |
||||
|
checkEntityId(query.getParameters().getEntityId()); |
||||
|
try { |
||||
|
List<EntityView> entityViews = checkNotNull(entityViewService.findEntityViewsByQuery(query).get()); |
||||
|
entityViews = entityViews.stream().filter(entityView -> { |
||||
|
try { |
||||
|
checkEntityView(entityView); |
||||
|
return true; |
||||
|
} catch (ThingsboardException e) { |
||||
|
return false; |
||||
|
} |
||||
|
}).collect(Collectors.toList()); |
||||
|
return entityViews; |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
||||
|
@RequestMapping(value = "/entityView/types", method = RequestMethod.GET) |
||||
|
@ResponseBody |
||||
|
public List<EntitySubtype> getEntityViewTypes() throws ThingsboardException { |
||||
|
try { |
||||
|
SecurityUser user = getCurrentUser(); |
||||
|
TenantId tenantId = user.getTenantId(); |
||||
|
ListenableFuture<List<EntitySubtype>> entityViewTypes = entityViewService.findEntityViewTypesByTenantId(tenantId); |
||||
|
return checkNotNull(entityViewTypes.get()); |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 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.service.cluster.routing; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.thingsboard.server.service.cluster.discovery.ServerInstance; |
||||
|
|
||||
|
import java.util.concurrent.ConcurrentNavigableMap; |
||||
|
import java.util.concurrent.ConcurrentSkipListMap; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 23.09.18. |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public class ConsistentHashCircle { |
||||
|
private final ConcurrentNavigableMap<Long, ServerInstance> circle = |
||||
|
new ConcurrentSkipListMap<>(); |
||||
|
|
||||
|
public void put(long hash, ServerInstance instance) { |
||||
|
circle.put(hash, instance); |
||||
|
} |
||||
|
|
||||
|
public void remove(long hash) { |
||||
|
circle.remove(hash); |
||||
|
} |
||||
|
|
||||
|
public boolean isEmpty() { |
||||
|
return circle.isEmpty(); |
||||
|
} |
||||
|
|
||||
|
public boolean containsKey(Long hash) { |
||||
|
return circle.containsKey(hash); |
||||
|
} |
||||
|
|
||||
|
public ConcurrentNavigableMap<Long, ServerInstance> tailMap(Long hash) { |
||||
|
return circle.tailMap(hash); |
||||
|
} |
||||
|
|
||||
|
public Long firstKey() { |
||||
|
return circle.firstKey(); |
||||
|
} |
||||
|
|
||||
|
public ServerInstance get(Long hash) { |
||||
|
return circle.get(hash); |
||||
|
} |
||||
|
|
||||
|
public void log() { |
||||
|
circle.entrySet().forEach((e) -> log.debug("{} -> {}", e.getKey(), e.getValue().getServerAddress())); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 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.service.install; |
||||
|
|
||||
|
import org.springframework.context.annotation.Profile; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.dao.util.NoSqlDao; |
||||
|
|
||||
|
@Service |
||||
|
@NoSqlDao |
||||
|
@Profile("install") |
||||
|
public class CassandraEntityDatabaseSchemaService extends CassandraAbstractDatabaseSchemaService |
||||
|
implements EntityDatabaseSchemaService { |
||||
|
public CassandraEntityDatabaseSchemaService() { |
||||
|
super("schema-entities.cql"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 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.service.install; |
||||
|
|
||||
|
import org.springframework.context.annotation.Profile; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.dao.util.NoSqlTsDao; |
||||
|
|
||||
|
@Service |
||||
|
@NoSqlTsDao |
||||
|
@Profile("install") |
||||
|
public class CassandraTsDatabaseSchemaService extends CassandraAbstractDatabaseSchemaService |
||||
|
implements TsDatabaseSchemaService { |
||||
|
public CassandraTsDatabaseSchemaService() { |
||||
|
super("schema-ts.cql"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 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.service.install; |
||||
|
|
||||
|
import org.springframework.context.annotation.Profile; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.dao.util.SqlDao; |
||||
|
|
||||
|
@Service |
||||
|
@SqlDao |
||||
|
@Profile("install") |
||||
|
public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService |
||||
|
implements EntityDatabaseSchemaService { |
||||
|
public SqlEntityDatabaseSchemaService() { |
||||
|
super("schema-entities.sql"); |
||||
|
} |
||||
|
} |
||||
@ -1,111 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 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.service.queue; |
|
||||
|
|
||||
import com.google.common.util.concurrent.Futures; |
|
||||
import com.google.common.util.concurrent.ListenableFuture; |
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
|
||||
import org.springframework.beans.factory.annotation.Value; |
|
||||
import org.springframework.stereotype.Service; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.msg.TbMsg; |
|
||||
import org.thingsboard.server.common.transport.quota.tenant.TenantQuotaService; |
|
||||
import org.thingsboard.server.dao.queue.MsgQueue; |
|
||||
|
|
||||
import javax.annotation.PostConstruct; |
|
||||
import javax.annotation.PreDestroy; |
|
||||
import java.util.Map; |
|
||||
import java.util.UUID; |
|
||||
import java.util.concurrent.ConcurrentHashMap; |
|
||||
import java.util.concurrent.Executors; |
|
||||
import java.util.concurrent.ScheduledExecutorService; |
|
||||
import java.util.concurrent.TimeUnit; |
|
||||
import java.util.concurrent.atomic.AtomicLong; |
|
||||
|
|
||||
@Service |
|
||||
@Slf4j |
|
||||
public class DefaultMsgQueueService implements MsgQueueService { |
|
||||
|
|
||||
@Value("${actors.rule.queue.max_size}") |
|
||||
private long queueMaxSize; |
|
||||
|
|
||||
@Value("${actors.rule.queue.cleanup_period}") |
|
||||
private long queueCleanUpPeriod; |
|
||||
|
|
||||
@Autowired |
|
||||
private MsgQueue msgQueue; |
|
||||
|
|
||||
@Autowired |
|
||||
private TenantQuotaService quotaService; |
|
||||
|
|
||||
private ScheduledExecutorService cleanupExecutor; |
|
||||
|
|
||||
private Map<TenantId, AtomicLong> pendingCountPerTenant = new ConcurrentHashMap<>(); |
|
||||
|
|
||||
@PostConstruct |
|
||||
public void init() { |
|
||||
if (queueCleanUpPeriod > 0) { |
|
||||
cleanupExecutor = Executors.newSingleThreadScheduledExecutor(); |
|
||||
cleanupExecutor.scheduleAtFixedRate(() -> cleanup(), |
|
||||
queueCleanUpPeriod, queueCleanUpPeriod, TimeUnit.SECONDS); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@PreDestroy |
|
||||
public void stop() { |
|
||||
if (cleanupExecutor != null) { |
|
||||
cleanupExecutor.shutdownNow(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public ListenableFuture<Void> put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) { |
|
||||
if(quotaService.isQuotaExceeded(tenantId.getId().toString())) { |
|
||||
log.warn("Tenant TbMsg Quota exceeded for [{}:{}] . Reject", tenantId.getId()); |
|
||||
return Futures.immediateFailedFuture(new RuntimeException("Tenant TbMsg Quota exceeded")); |
|
||||
} |
|
||||
|
|
||||
AtomicLong pendingMsgCount = pendingCountPerTenant.computeIfAbsent(tenantId, key -> new AtomicLong()); |
|
||||
if (pendingMsgCount.incrementAndGet() < queueMaxSize) { |
|
||||
return msgQueue.put(tenantId, msg, nodeId, clusterPartition); |
|
||||
} else { |
|
||||
pendingMsgCount.decrementAndGet(); |
|
||||
return Futures.immediateFailedFuture(new RuntimeException("Message queue is full!")); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public ListenableFuture<Void> ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) { |
|
||||
ListenableFuture<Void> result = msgQueue.ack(tenantId, msg, nodeId, clusterPartition); |
|
||||
AtomicLong pendingMsgCount = pendingCountPerTenant.computeIfAbsent(tenantId, key -> new AtomicLong()); |
|
||||
pendingMsgCount.decrementAndGet(); |
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public Iterable<TbMsg> findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition) { |
|
||||
return msgQueue.findUnprocessed(tenantId, nodeId, clusterPartition); |
|
||||
} |
|
||||
|
|
||||
private void cleanup() { |
|
||||
pendingCountPerTenant.forEach((tenantId, pendingMsgCount) -> { |
|
||||
pendingMsgCount.set(0); |
|
||||
msgQueue.cleanUp(tenantId); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,32 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 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.service.queue; |
|
||||
|
|
||||
import com.google.common.util.concurrent.ListenableFuture; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.msg.TbMsg; |
|
||||
|
|
||||
import java.util.UUID; |
|
||||
|
|
||||
public interface MsgQueueService { |
|
||||
|
|
||||
ListenableFuture<Void> put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition); |
|
||||
|
|
||||
ListenableFuture<Void> ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition); |
|
||||
|
|
||||
Iterable<TbMsg> findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition); |
|
||||
|
|
||||
} |
|
||||
@ -0,0 +1,102 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 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.service.script; |
||||
|
|
||||
|
import com.google.common.util.concurrent.Futures; |
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
|
||||
|
import java.util.Map; |
||||
|
import java.util.UUID; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
import java.util.concurrent.atomic.AtomicInteger; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 26.09.18. |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public abstract class AbstractJsInvokeService implements JsInvokeService { |
||||
|
|
||||
|
protected Map<UUID, String> scriptIdToNameMap = new ConcurrentHashMap<>(); |
||||
|
protected Map<UUID, AtomicInteger> blackListedFunctions = new ConcurrentHashMap<>(); |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<UUID> eval(JsScriptType scriptType, String scriptBody, String... argNames) { |
||||
|
UUID scriptId = UUID.randomUUID(); |
||||
|
String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_'); |
||||
|
String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames); |
||||
|
return doEval(scriptId, functionName, jsScript); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<Object> invokeFunction(UUID scriptId, Object... args) { |
||||
|
String functionName = scriptIdToNameMap.get(scriptId); |
||||
|
if (functionName == null) { |
||||
|
return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!")); |
||||
|
} |
||||
|
if (!isBlackListed(scriptId)) { |
||||
|
return doInvokeFunction(scriptId, functionName, args); |
||||
|
} else { |
||||
|
return Futures.immediateFailedFuture( |
||||
|
new RuntimeException("Script is blacklisted due to maximum error count " + getMaxErrors() + "!")); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListenableFuture<Void> release(UUID scriptId) { |
||||
|
String functionName = scriptIdToNameMap.get(scriptId); |
||||
|
if (functionName != null) { |
||||
|
try { |
||||
|
scriptIdToNameMap.remove(scriptId); |
||||
|
blackListedFunctions.remove(scriptId); |
||||
|
doRelease(scriptId, functionName); |
||||
|
} catch (Exception e) { |
||||
|
return Futures.immediateFailedFuture(e); |
||||
|
} |
||||
|
} |
||||
|
return Futures.immediateFuture(null); |
||||
|
} |
||||
|
|
||||
|
protected abstract ListenableFuture<UUID> doEval(UUID scriptId, String functionName, String scriptBody); |
||||
|
|
||||
|
protected abstract ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args); |
||||
|
|
||||
|
protected abstract void doRelease(UUID scriptId, String functionName) throws Exception; |
||||
|
|
||||
|
protected abstract int getMaxErrors(); |
||||
|
|
||||
|
protected void onScriptExecutionError(UUID scriptId) { |
||||
|
blackListedFunctions.computeIfAbsent(scriptId, key -> new AtomicInteger(0)).incrementAndGet(); |
||||
|
} |
||||
|
|
||||
|
private String generateJsScript(JsScriptType scriptType, String functionName, String scriptBody, String... argNames) { |
||||
|
switch (scriptType) { |
||||
|
case RULE_NODE_SCRIPT: |
||||
|
return RuleNodeScriptFactory.generateRuleNodeScript(functionName, scriptBody, argNames); |
||||
|
default: |
||||
|
throw new RuntimeException("No script factory implemented for scriptType: " + scriptType); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private boolean isBlackListed(UUID scriptId) { |
||||
|
if (blackListedFunctions.containsKey(scriptId)) { |
||||
|
AtomicInteger errorCount = blackListedFunctions.get(scriptId); |
||||
|
return errorCount.get() >= getMaxErrors(); |
||||
|
} else { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,109 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 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.service.script; |
||||
|
|
||||
|
import com.google.common.util.concurrent.Futures; |
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
import delight.nashornsandbox.NashornSandbox; |
||||
|
import delight.nashornsandbox.NashornSandboxes; |
||||
|
import jdk.nashorn.api.scripting.NashornScriptEngineFactory; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
|
||||
|
import javax.annotation.PostConstruct; |
||||
|
import javax.annotation.PreDestroy; |
||||
|
import javax.script.Invocable; |
||||
|
import javax.script.ScriptEngine; |
||||
|
import javax.script.ScriptException; |
||||
|
import java.util.UUID; |
||||
|
import java.util.concurrent.ExecutorService; |
||||
|
import java.util.concurrent.Executors; |
||||
|
|
||||
|
@Slf4j |
||||
|
public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeService { |
||||
|
|
||||
|
private NashornSandbox sandbox; |
||||
|
private ScriptEngine engine; |
||||
|
private ExecutorService monitorExecutorService; |
||||
|
|
||||
|
@PostConstruct |
||||
|
public void init() { |
||||
|
if (useJsSandbox()) { |
||||
|
sandbox = NashornSandboxes.create(); |
||||
|
monitorExecutorService = Executors.newFixedThreadPool(getMonitorThreadPoolSize()); |
||||
|
sandbox.setExecutor(monitorExecutorService); |
||||
|
sandbox.setMaxCPUTime(getMaxCpuTime()); |
||||
|
sandbox.allowNoBraces(false); |
||||
|
sandbox.setMaxPreparedStatements(30); |
||||
|
} else { |
||||
|
NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); |
||||
|
engine = factory.getScriptEngine(new String[]{"--no-java"}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@PreDestroy |
||||
|
public void stop() { |
||||
|
if (monitorExecutorService != null) { |
||||
|
monitorExecutorService.shutdownNow(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected abstract boolean useJsSandbox(); |
||||
|
|
||||
|
protected abstract int getMonitorThreadPoolSize(); |
||||
|
|
||||
|
protected abstract long getMaxCpuTime(); |
||||
|
|
||||
|
@Override |
||||
|
protected ListenableFuture<UUID> doEval(UUID scriptId, String functionName, String jsScript) { |
||||
|
try { |
||||
|
if (useJsSandbox()) { |
||||
|
sandbox.eval(jsScript); |
||||
|
} else { |
||||
|
engine.eval(jsScript); |
||||
|
} |
||||
|
scriptIdToNameMap.put(scriptId, functionName); |
||||
|
} catch (Exception e) { |
||||
|
log.warn("Failed to compile JS script: {}", e.getMessage(), e); |
||||
|
return Futures.immediateFailedFuture(e); |
||||
|
} |
||||
|
return Futures.immediateFuture(scriptId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args) { |
||||
|
try { |
||||
|
Object result; |
||||
|
if (useJsSandbox()) { |
||||
|
result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args); |
||||
|
} else { |
||||
|
result = ((Invocable) engine).invokeFunction(functionName, args); |
||||
|
} |
||||
|
return Futures.immediateFuture(result); |
||||
|
} catch (Exception e) { |
||||
|
onScriptExecutionError(scriptId); |
||||
|
return Futures.immediateFailedFuture(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected void doRelease(UUID scriptId, String functionName) throws ScriptException { |
||||
|
if (useJsSandbox()) { |
||||
|
sandbox.eval(functionName + " = undefined;"); |
||||
|
} else { |
||||
|
engine.eval(functionName + " = undefined;"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -1,188 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 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.service.script; |
|
||||
|
|
||||
import com.google.common.util.concurrent.Futures; |
|
||||
import com.google.common.util.concurrent.ListenableFuture; |
|
||||
import delight.nashornsandbox.NashornSandbox; |
|
||||
import delight.nashornsandbox.NashornSandboxes; |
|
||||
import jdk.nashorn.api.scripting.NashornScriptEngineFactory; |
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.apache.commons.lang3.tuple.Pair; |
|
||||
|
|
||||
import javax.annotation.PostConstruct; |
|
||||
import javax.annotation.PreDestroy; |
|
||||
import javax.script.Invocable; |
|
||||
import javax.script.ScriptEngine; |
|
||||
import javax.script.ScriptException; |
|
||||
import java.util.Map; |
|
||||
import java.util.UUID; |
|
||||
import java.util.concurrent.ConcurrentHashMap; |
|
||||
import java.util.concurrent.ExecutorService; |
|
||||
import java.util.concurrent.Executors; |
|
||||
import java.util.concurrent.atomic.AtomicInteger; |
|
||||
|
|
||||
@Slf4j |
|
||||
public abstract class AbstractNashornJsSandboxService implements JsSandboxService { |
|
||||
|
|
||||
private NashornSandbox sandbox; |
|
||||
private ScriptEngine engine; |
|
||||
private ExecutorService monitorExecutorService; |
|
||||
|
|
||||
private final Map<UUID, String> functionsMap = new ConcurrentHashMap<>(); |
|
||||
private final Map<UUID,AtomicInteger> blackListedFunctions = new ConcurrentHashMap<>(); |
|
||||
private final Map<String, Pair<UUID, AtomicInteger>> scriptToId = new ConcurrentHashMap<>(); |
|
||||
private final Map<UUID, AtomicInteger> scriptIdToCount = new ConcurrentHashMap<>(); |
|
||||
|
|
||||
@PostConstruct |
|
||||
public void init() { |
|
||||
if (useJsSandbox()) { |
|
||||
sandbox = NashornSandboxes.create(); |
|
||||
monitorExecutorService = Executors.newFixedThreadPool(getMonitorThreadPoolSize()); |
|
||||
sandbox.setExecutor(monitorExecutorService); |
|
||||
sandbox.setMaxCPUTime(getMaxCpuTime()); |
|
||||
sandbox.allowNoBraces(false); |
|
||||
sandbox.setMaxPreparedStatements(30); |
|
||||
} else { |
|
||||
NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); |
|
||||
engine = factory.getScriptEngine(new String[]{"--no-java"}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@PreDestroy |
|
||||
public void stop() { |
|
||||
if (monitorExecutorService != null) { |
|
||||
monitorExecutorService.shutdownNow(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected abstract boolean useJsSandbox(); |
|
||||
|
|
||||
protected abstract int getMonitorThreadPoolSize(); |
|
||||
|
|
||||
protected abstract long getMaxCpuTime(); |
|
||||
|
|
||||
protected abstract int getMaxErrors(); |
|
||||
|
|
||||
@Override |
|
||||
public ListenableFuture<UUID> eval(JsScriptType scriptType, String scriptBody, String... argNames) { |
|
||||
Pair<UUID, AtomicInteger> deduplicated = deduplicate(scriptType, scriptBody); |
|
||||
UUID scriptId = deduplicated.getLeft(); |
|
||||
AtomicInteger duplicateCount = deduplicated.getRight(); |
|
||||
|
|
||||
if(duplicateCount.compareAndSet(0, 1)) { |
|
||||
String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_'); |
|
||||
String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames); |
|
||||
try { |
|
||||
if (useJsSandbox()) { |
|
||||
sandbox.eval(jsScript); |
|
||||
} else { |
|
||||
engine.eval(jsScript); |
|
||||
} |
|
||||
functionsMap.put(scriptId, functionName); |
|
||||
} catch (Exception e) { |
|
||||
duplicateCount.decrementAndGet(); |
|
||||
log.warn("Failed to compile JS script: {}", e.getMessage(), e); |
|
||||
return Futures.immediateFailedFuture(e); |
|
||||
} |
|
||||
} else { |
|
||||
duplicateCount.incrementAndGet(); |
|
||||
} |
|
||||
return Futures.immediateFuture(scriptId); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public ListenableFuture<Object> invokeFunction(UUID scriptId, Object... args) { |
|
||||
String functionName = functionsMap.get(scriptId); |
|
||||
if (functionName == null) { |
|
||||
return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!")); |
|
||||
} |
|
||||
if (!isBlackListed(scriptId)) { |
|
||||
try { |
|
||||
Object result; |
|
||||
if (useJsSandbox()) { |
|
||||
result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args); |
|
||||
} else { |
|
||||
result = ((Invocable)engine).invokeFunction(functionName, args); |
|
||||
} |
|
||||
return Futures.immediateFuture(result); |
|
||||
} catch (Exception e) { |
|
||||
blackListedFunctions.computeIfAbsent(scriptId, key -> new AtomicInteger(0)).incrementAndGet(); |
|
||||
return Futures.immediateFailedFuture(e); |
|
||||
} |
|
||||
} else { |
|
||||
return Futures.immediateFailedFuture( |
|
||||
new RuntimeException("Script is blacklisted due to maximum error count " + getMaxErrors() + "!")); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public ListenableFuture<Void> release(UUID scriptId) { |
|
||||
AtomicInteger count = scriptIdToCount.get(scriptId); |
|
||||
if(count != null) { |
|
||||
if(count.decrementAndGet() > 0) { |
|
||||
return Futures.immediateFuture(null); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
String functionName = functionsMap.get(scriptId); |
|
||||
if (functionName != null) { |
|
||||
try { |
|
||||
if (useJsSandbox()) { |
|
||||
sandbox.eval(functionName + " = undefined;"); |
|
||||
} else { |
|
||||
engine.eval(functionName + " = undefined;"); |
|
||||
} |
|
||||
functionsMap.remove(scriptId); |
|
||||
blackListedFunctions.remove(scriptId); |
|
||||
} catch (ScriptException e) { |
|
||||
return Futures.immediateFailedFuture(e); |
|
||||
} |
|
||||
} |
|
||||
return Futures.immediateFuture(null); |
|
||||
} |
|
||||
|
|
||||
private boolean isBlackListed(UUID scriptId) { |
|
||||
if (blackListedFunctions.containsKey(scriptId)) { |
|
||||
AtomicInteger errorCount = blackListedFunctions.get(scriptId); |
|
||||
return errorCount.get() >= getMaxErrors(); |
|
||||
} else { |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private String generateJsScript(JsScriptType scriptType, String functionName, String scriptBody, String... argNames) { |
|
||||
switch (scriptType) { |
|
||||
case RULE_NODE_SCRIPT: |
|
||||
return RuleNodeScriptFactory.generateRuleNodeScript(functionName, scriptBody, argNames); |
|
||||
default: |
|
||||
throw new RuntimeException("No script factory implemented for scriptType: " + scriptType); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private Pair<UUID, AtomicInteger> deduplicate(JsScriptType scriptType, String scriptBody) { |
|
||||
Pair<UUID, AtomicInteger> precomputed = Pair.of(UUID.randomUUID(), new AtomicInteger()); |
|
||||
|
|
||||
Pair<UUID, AtomicInteger> pair = scriptToId.computeIfAbsent(deduplicateKey(scriptType, scriptBody), i -> precomputed); |
|
||||
AtomicInteger duplicateCount = scriptIdToCount.computeIfAbsent(pair.getLeft(), i -> pair.getRight()); |
|
||||
return Pair.of(pair.getLeft(), duplicateCount); |
|
||||
} |
|
||||
|
|
||||
private String deduplicateKey(JsScriptType scriptType, String scriptBody) { |
|
||||
return scriptType + "_" + scriptBody; |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,29 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 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.service.script; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 25.09.18. |
||||
|
*/ |
||||
|
public class JsInvokeResponse { |
||||
|
|
||||
|
private String scriptId; |
||||
|
private String scriptBody; |
||||
|
private List<String> args; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,211 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 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.service.script; |
||||
|
|
||||
|
import com.google.common.util.concurrent.Futures; |
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
import lombok.Getter; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.gen.js.JsInvokeProtos; |
||||
|
import org.thingsboard.server.kafka.TBKafkaConsumerTemplate; |
||||
|
import org.thingsboard.server.kafka.TBKafkaProducerTemplate; |
||||
|
import org.thingsboard.server.kafka.TbKafkaRequestTemplate; |
||||
|
import org.thingsboard.server.kafka.TbKafkaSettings; |
||||
|
import org.thingsboard.server.kafka.TbNodeIdProvider; |
||||
|
import org.thingsboard.server.service.cluster.discovery.DiscoveryService; |
||||
|
|
||||
|
import javax.annotation.PostConstruct; |
||||
|
import javax.annotation.PreDestroy; |
||||
|
import java.util.Map; |
||||
|
import java.util.UUID; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
|
||||
|
@Slf4j |
||||
|
@ConditionalOnProperty(prefix = "js", value = "evaluator", havingValue = "remote", matchIfMissing = true) |
||||
|
@Service |
||||
|
public class RemoteJsInvokeService extends AbstractJsInvokeService { |
||||
|
|
||||
|
@Autowired |
||||
|
private TbNodeIdProvider nodeIdProvider; |
||||
|
|
||||
|
@Autowired |
||||
|
private TbKafkaSettings kafkaSettings; |
||||
|
|
||||
|
@Value("${js.remote.request_topic}") |
||||
|
private String requestTopic; |
||||
|
|
||||
|
@Value("${js.remote.response_topic_prefix}") |
||||
|
private String responseTopicPrefix; |
||||
|
|
||||
|
@Value("${js.remote.max_pending_requests}") |
||||
|
private long maxPendingRequests; |
||||
|
|
||||
|
@Value("${js.remote.max_requests_timeout}") |
||||
|
private long maxRequestsTimeout; |
||||
|
|
||||
|
@Value("${js.remote.response_poll_interval}") |
||||
|
private int responsePollDuration; |
||||
|
|
||||
|
@Value("${js.remote.response_auto_commit_interval}") |
||||
|
private int autoCommitInterval; |
||||
|
|
||||
|
@Getter |
||||
|
@Value("${js.remote.max_errors}") |
||||
|
private int maxErrors; |
||||
|
|
||||
|
private TbKafkaRequestTemplate<JsInvokeProtos.RemoteJsRequest, JsInvokeProtos.RemoteJsResponse> kafkaTemplate; |
||||
|
private Map<UUID, String> scriptIdToBodysMap = new ConcurrentHashMap<>(); |
||||
|
|
||||
|
@PostConstruct |
||||
|
public void init() { |
||||
|
TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder<JsInvokeProtos.RemoteJsRequest> requestBuilder = TBKafkaProducerTemplate.builder(); |
||||
|
requestBuilder.settings(kafkaSettings); |
||||
|
requestBuilder.defaultTopic(requestTopic); |
||||
|
requestBuilder.encoder(new RemoteJsRequestEncoder()); |
||||
|
requestBuilder.enricher((request, responseTopic, requestId) -> { |
||||
|
JsInvokeProtos.RemoteJsRequest.Builder remoteRequest = JsInvokeProtos.RemoteJsRequest.newBuilder(); |
||||
|
if (request.hasCompileRequest()) { |
||||
|
remoteRequest.setCompileRequest(request.getCompileRequest()); |
||||
|
} |
||||
|
if (request.hasInvokeRequest()) { |
||||
|
remoteRequest.setInvokeRequest(request.getInvokeRequest()); |
||||
|
} |
||||
|
if (request.hasReleaseRequest()) { |
||||
|
remoteRequest.setReleaseRequest(request.getReleaseRequest()); |
||||
|
} |
||||
|
remoteRequest.setResponseTopic(responseTopic); |
||||
|
remoteRequest.setRequestIdMSB(requestId.getMostSignificantBits()); |
||||
|
remoteRequest.setRequestIdLSB(requestId.getLeastSignificantBits()); |
||||
|
return remoteRequest.build(); |
||||
|
}); |
||||
|
|
||||
|
TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder<JsInvokeProtos.RemoteJsResponse> responseBuilder = TBKafkaConsumerTemplate.builder(); |
||||
|
responseBuilder.settings(kafkaSettings); |
||||
|
responseBuilder.topic(responseTopicPrefix + "." + nodeIdProvider.getNodeId()); |
||||
|
responseBuilder.clientId("js-" + nodeIdProvider.getNodeId()); |
||||
|
responseBuilder.groupId("rule-engine-node-" + nodeIdProvider.getNodeId()); |
||||
|
responseBuilder.autoCommit(true); |
||||
|
responseBuilder.autoCommitIntervalMs(autoCommitInterval); |
||||
|
responseBuilder.decoder(new RemoteJsResponseDecoder()); |
||||
|
responseBuilder.requestIdExtractor((response) -> new UUID(response.getRequestIdMSB(), response.getRequestIdLSB())); |
||||
|
|
||||
|
TbKafkaRequestTemplate.TbKafkaRequestTemplateBuilder |
||||
|
<JsInvokeProtos.RemoteJsRequest, JsInvokeProtos.RemoteJsResponse> builder = TbKafkaRequestTemplate.builder(); |
||||
|
builder.requestTemplate(requestBuilder.build()); |
||||
|
builder.responseTemplate(responseBuilder.build()); |
||||
|
builder.maxPendingRequests(maxPendingRequests); |
||||
|
builder.maxRequestTimeout(maxRequestsTimeout); |
||||
|
builder.pollInterval(responsePollDuration); |
||||
|
kafkaTemplate = builder.build(); |
||||
|
kafkaTemplate.init(); |
||||
|
} |
||||
|
|
||||
|
@PreDestroy |
||||
|
public void destroy() { |
||||
|
if (kafkaTemplate != null) { |
||||
|
kafkaTemplate.stop(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected ListenableFuture<UUID> doEval(UUID scriptId, String functionName, String scriptBody) { |
||||
|
JsInvokeProtos.JsCompileRequest jsRequest = JsInvokeProtos.JsCompileRequest.newBuilder() |
||||
|
.setScriptIdMSB(scriptId.getMostSignificantBits()) |
||||
|
.setScriptIdLSB(scriptId.getLeastSignificantBits()) |
||||
|
.setFunctionName(functionName) |
||||
|
.setScriptBody(scriptBody).build(); |
||||
|
|
||||
|
JsInvokeProtos.RemoteJsRequest jsRequestWrapper = JsInvokeProtos.RemoteJsRequest.newBuilder() |
||||
|
.setCompileRequest(jsRequest) |
||||
|
.build(); |
||||
|
|
||||
|
log.trace("Post compile request for scriptId [{}]", scriptId); |
||||
|
ListenableFuture<JsInvokeProtos.RemoteJsResponse> future = kafkaTemplate.post(scriptId.toString(), jsRequestWrapper); |
||||
|
return Futures.transform(future, response -> { |
||||
|
JsInvokeProtos.JsCompileResponse compilationResult = response.getCompileResponse(); |
||||
|
UUID compiledScriptId = new UUID(compilationResult.getScriptIdMSB(), compilationResult.getScriptIdLSB()); |
||||
|
if (compilationResult.getSuccess()) { |
||||
|
scriptIdToNameMap.put(scriptId, functionName); |
||||
|
scriptIdToBodysMap.put(scriptId, scriptBody); |
||||
|
return compiledScriptId; |
||||
|
} else { |
||||
|
log.debug("[{}] Failed to compile script due to [{}]: {}", compiledScriptId, compilationResult.getErrorCode().name(), compilationResult.getErrorDetails()); |
||||
|
throw new RuntimeException(compilationResult.getErrorDetails()); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args) { |
||||
|
String scriptBody = scriptIdToBodysMap.get(scriptId); |
||||
|
if (scriptBody == null) { |
||||
|
return Futures.immediateFailedFuture(new RuntimeException("No script body found for scriptId: [" + scriptId + "]!")); |
||||
|
} |
||||
|
JsInvokeProtos.JsInvokeRequest.Builder jsRequestBuilder = JsInvokeProtos.JsInvokeRequest.newBuilder() |
||||
|
.setScriptIdMSB(scriptId.getMostSignificantBits()) |
||||
|
.setScriptIdLSB(scriptId.getLeastSignificantBits()) |
||||
|
.setFunctionName(functionName) |
||||
|
.setTimeout((int) maxRequestsTimeout) |
||||
|
.setScriptBody(scriptIdToBodysMap.get(scriptId)); |
||||
|
|
||||
|
for (int i = 0; i < args.length; i++) { |
||||
|
jsRequestBuilder.addArgs(args[i].toString()); |
||||
|
} |
||||
|
|
||||
|
JsInvokeProtos.RemoteJsRequest jsRequestWrapper = JsInvokeProtos.RemoteJsRequest.newBuilder() |
||||
|
.setInvokeRequest(jsRequestBuilder.build()) |
||||
|
.build(); |
||||
|
|
||||
|
ListenableFuture<JsInvokeProtos.RemoteJsResponse> future = kafkaTemplate.post(scriptId.toString(), jsRequestWrapper); |
||||
|
return Futures.transform(future, response -> { |
||||
|
JsInvokeProtos.JsInvokeResponse invokeResult = response.getInvokeResponse(); |
||||
|
if (invokeResult.getSuccess()) { |
||||
|
return invokeResult.getResult(); |
||||
|
} else { |
||||
|
log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails()); |
||||
|
throw new RuntimeException(invokeResult.getErrorDetails()); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void doRelease(UUID scriptId, String functionName) throws Exception { |
||||
|
JsInvokeProtos.JsReleaseRequest jsRequest = JsInvokeProtos.JsReleaseRequest.newBuilder() |
||||
|
.setScriptIdMSB(scriptId.getMostSignificantBits()) |
||||
|
.setScriptIdLSB(scriptId.getLeastSignificantBits()) |
||||
|
.setFunctionName(functionName).build(); |
||||
|
|
||||
|
JsInvokeProtos.RemoteJsRequest jsRequestWrapper = JsInvokeProtos.RemoteJsRequest.newBuilder() |
||||
|
.setReleaseRequest(jsRequest) |
||||
|
.build(); |
||||
|
|
||||
|
ListenableFuture<JsInvokeProtos.RemoteJsResponse> future = kafkaTemplate.post(scriptId.toString(), jsRequestWrapper); |
||||
|
JsInvokeProtos.RemoteJsResponse response = future.get(); |
||||
|
|
||||
|
JsInvokeProtos.JsReleaseResponse compilationResult = response.getReleaseResponse(); |
||||
|
UUID compiledScriptId = new UUID(compilationResult.getScriptIdMSB(), compilationResult.getScriptIdLSB()); |
||||
|
if (compilationResult.getSuccess()) { |
||||
|
scriptIdToBodysMap.remove(scriptId); |
||||
|
} else { |
||||
|
log.debug("[{}] Failed to release script due", compiledScriptId); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 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.service.script; |
||||
|
|
||||
|
import org.thingsboard.server.gen.js.JsInvokeProtos; |
||||
|
import org.thingsboard.server.kafka.TbKafkaDecoder; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 25.09.18. |
||||
|
*/ |
||||
|
public class RemoteJsResponseDecoder implements TbKafkaDecoder<JsInvokeProtos.RemoteJsResponse> { |
||||
|
|
||||
|
@Override |
||||
|
public JsInvokeProtos.RemoteJsResponse decode(byte[] data) throws IOException { |
||||
|
return JsInvokeProtos.RemoteJsResponse.parseFrom(data); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 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.service.session; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.cache.annotation.CachePut; |
||||
|
import org.springframework.cache.annotation.Cacheable; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.Collections; |
||||
|
|
||||
|
import static org.thingsboard.server.common.data.CacheConstants.SESSIONS_CACHE; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 29.10.18. |
||||
|
*/ |
||||
|
@Service |
||||
|
@Slf4j |
||||
|
public class DefaultDeviceSessionCacheService implements DeviceSessionCacheService { |
||||
|
|
||||
|
@Override |
||||
|
@Cacheable(cacheNames = SESSIONS_CACHE, key = "#deviceId") |
||||
|
public DeviceSessionsCacheEntry get(DeviceId deviceId) { |
||||
|
log.debug("[{}] Fetching session data from cache", deviceId); |
||||
|
return DeviceSessionsCacheEntry.newBuilder().addAllSessions(Collections.emptyList()).build(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
@CachePut(cacheNames = SESSIONS_CACHE, key = "#deviceId") |
||||
|
public DeviceSessionsCacheEntry put(DeviceId deviceId, DeviceSessionsCacheEntry sessions) { |
||||
|
log.debug("[{}] Pushing session data from cache: {}", deviceId, sessions); |
||||
|
return sessions; |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue