Commit 933c502ce3878ca511f1b5e4c05b2260dc6978a8

Authored by Andrii Shvaika
2 parents c2e36851 dc151ace

Merge remote-tracking branch 'origin/master'

@@ -75,7 +75,8 @@ CREATE OR REPLACE PROCEDURE drop_all_idx() @@ -75,7 +75,8 @@ CREATE OR REPLACE PROCEDURE drop_all_idx()
75 $$ 75 $$
76 BEGIN 76 BEGIN
77 DROP INDEX IF EXISTS idx_alarm_originator_alarm_type; 77 DROP INDEX IF EXISTS idx_alarm_originator_alarm_type;
78 - DROP INDEX IF EXISTS idx_alarm_originator_alarm_time; 78 + DROP INDEX IF EXISTS idx_alarm_originator_created_time;
  79 + DROP INDEX IF EXISTS idx_alarm_tenant_created_time;
79 DROP INDEX IF EXISTS idx_event_type_entity_id; 80 DROP INDEX IF EXISTS idx_event_type_entity_id;
80 DROP INDEX IF EXISTS idx_relation_to_id; 81 DROP INDEX IF EXISTS idx_relation_to_id;
81 DROP INDEX IF EXISTS idx_relation_from_id; 82 DROP INDEX IF EXISTS idx_relation_from_id;
@@ -93,7 +94,8 @@ CREATE OR REPLACE PROCEDURE create_all_idx() @@ -93,7 +94,8 @@ CREATE OR REPLACE PROCEDURE create_all_idx()
93 $$ 94 $$
94 BEGIN 95 BEGIN
95 CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type ON alarm(originator_id, type, start_ts DESC); 96 CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type ON alarm(originator_id, type, start_ts DESC);
96 - CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_time ON alarm(originator_id, created_time DESC); 97 + CREATE INDEX IF NOT EXISTS idx_alarm_originator_created_time ON alarm(originator_id, created_time DESC);
  98 + CREATE INDEX IF NOT EXISTS idx_alarm_tenant_created_time ON alarm(tenant_id, created_time DESC);
97 CREATE INDEX IF NOT EXISTS idx_event_type_entity_id ON event(tenant_id, event_type, entity_type, entity_id); 99 CREATE INDEX IF NOT EXISTS idx_event_type_entity_id ON event(tenant_id, event_type, entity_type, entity_id);
98 CREATE INDEX IF NOT EXISTS idx_relation_to_id ON relation(relation_type_group, to_type, to_id); 100 CREATE INDEX IF NOT EXISTS idx_relation_to_id ON relation(relation_type_group, to_type, to_id);
99 CREATE INDEX IF NOT EXISTS idx_relation_from_id ON relation(relation_type_group, from_type, from_id); 101 CREATE INDEX IF NOT EXISTS idx_relation_from_id ON relation(relation_type_group, from_type, from_id);
@@ -286,7 +286,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc @@ -286,7 +286,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
286 int dynamicQueryInvocationCntValue = stats.getDynamicQueryInvocationCnt().getAndSet(0); 286 int dynamicQueryInvocationCntValue = stats.getDynamicQueryInvocationCnt().getAndSet(0);
287 long dynamicQueryInvocationTimeValue = stats.getDynamicQueryTimeSpent().getAndSet(0); 287 long dynamicQueryInvocationTimeValue = stats.getDynamicQueryTimeSpent().getAndSet(0);
288 long dynamicQueryCnt = subscriptionsBySessionId.values().stream().map(Map::values).count(); 288 long dynamicQueryCnt = subscriptionsBySessionId.values().stream().map(Map::values).count();
289 - if (regularQueryInvocationCntValue > 0 || dynamicQueryInvocationCntValue > 0 || dynamicQueryCnt > 0) { 289 + if (regularQueryInvocationCntValue > 0 || dynamicQueryInvocationCntValue > 0 || dynamicQueryCnt > 0 || alarmQueryInvocationCntValue > 0) {
290 log.info("Stats: regularQueryInvocationCnt = [{}], regularQueryInvocationTime = [{}], " + 290 log.info("Stats: regularQueryInvocationCnt = [{}], regularQueryInvocationTime = [{}], " +
291 "dynamicQueryCnt = [{}] dynamicQueryInvocationCnt = [{}], dynamicQueryInvocationTime = [{}], " + 291 "dynamicQueryCnt = [{}] dynamicQueryInvocationCnt = [{}], dynamicQueryInvocationTime = [{}], " +
292 "alarmQueryInvocationCnt = [{}], alarmQueryInvocationTime = [{}]", 292 "alarmQueryInvocationCnt = [{}], alarmQueryInvocationTime = [{}]",
@@ -58,6 +58,9 @@ import org.thingsboard.server.service.queue.TbClusterService; @@ -58,6 +58,9 @@ import org.thingsboard.server.service.queue.TbClusterService;
58 import org.thingsboard.server.service.state.DeviceStateService; 58 import org.thingsboard.server.service.state.DeviceStateService;
59 59
60 import java.util.UUID; 60 import java.util.UUID;
  61 +import java.util.concurrent.ConcurrentHashMap;
  62 +import java.util.concurrent.ConcurrentMap;
  63 +import java.util.concurrent.locks.Lock;
61 import java.util.concurrent.locks.ReentrantLock; 64 import java.util.concurrent.locks.ReentrantLock;
62 65
63 /** 66 /**
@@ -92,7 +95,7 @@ public class DefaultTransportApiService implements TransportApiService { @@ -92,7 +95,7 @@ public class DefaultTransportApiService implements TransportApiService {
92 @Autowired 95 @Autowired
93 protected TbClusterService tbClusterService; 96 protected TbClusterService tbClusterService;
94 97
95 - private ReentrantLock deviceCreationLock = new ReentrantLock(); 98 + private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
96 99
97 @Override 100 @Override
98 public ListenableFuture<TbProtoQueueMsg<TransportApiResponseMsg>> handle(TbProtoQueueMsg<TransportApiRequestMsg> tbProtoQueueMsg) { 101 public ListenableFuture<TbProtoQueueMsg<TransportApiResponseMsg>> handle(TbProtoQueueMsg<TransportApiRequestMsg> tbProtoQueueMsg) {
@@ -125,6 +128,7 @@ public class DefaultTransportApiService implements TransportApiService { @@ -125,6 +128,7 @@ public class DefaultTransportApiService implements TransportApiService {
125 DeviceId gatewayId = new DeviceId(new UUID(requestMsg.getGatewayIdMSB(), requestMsg.getGatewayIdLSB())); 128 DeviceId gatewayId = new DeviceId(new UUID(requestMsg.getGatewayIdMSB(), requestMsg.getGatewayIdLSB()));
126 ListenableFuture<Device> gatewayFuture = deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, gatewayId); 129 ListenableFuture<Device> gatewayFuture = deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, gatewayId);
127 return Futures.transform(gatewayFuture, gateway -> { 130 return Futures.transform(gatewayFuture, gateway -> {
  131 + Lock deviceCreationLock = deviceCreationLocks.computeIfAbsent(requestMsg.getDeviceName(), id -> new ReentrantLock());
128 deviceCreationLock.lock(); 132 deviceCreationLock.lock();
129 try { 133 try {
130 Device device = deviceService.findDeviceByTenantIdAndName(gateway.getTenantId(), requestMsg.getDeviceName()); 134 Device device = deviceService.findDeviceByTenantIdAndName(gateway.getTenantId(), requestMsg.getDeviceName());
@@ -20,6 +20,9 @@ import org.apache.commons.lang3.StringUtils; @@ -20,6 +20,9 @@ import org.apache.commons.lang3.StringUtils;
20 import org.springframework.beans.factory.annotation.Autowired; 20 import org.springframework.beans.factory.annotation.Autowired;
21 import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 21 import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
22 import org.springframework.stereotype.Repository; 22 import org.springframework.stereotype.Repository;
  23 +import org.springframework.transaction.TransactionStatus;
  24 +import org.springframework.transaction.support.TransactionCallback;
  25 +import org.springframework.transaction.support.TransactionTemplate;
23 import org.thingsboard.server.common.data.EntityType; 26 import org.thingsboard.server.common.data.EntityType;
24 import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; 27 import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
25 import org.thingsboard.server.common.data.alarm.AlarmSeverity; 28 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
@@ -53,7 +56,6 @@ import java.util.stream.Collectors; @@ -53,7 +56,6 @@ import java.util.stream.Collectors;
53 public class DefaultAlarmQueryRepository implements AlarmQueryRepository { 56 public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
54 57
55 private static final Map<String, String> alarmFieldColumnMap = new HashMap<>(); 58 private static final Map<String, String> alarmFieldColumnMap = new HashMap<>();
56 - private static final List<String> uniqueAlarmFields = new ArrayList<>();  
57 59
58 static { 60 static {
59 alarmFieldColumnMap.put("createdTime", ModelConstants.CREATED_TIME_PROPERTY); 61 alarmFieldColumnMap.put("createdTime", ModelConstants.CREATED_TIME_PROPERTY);
@@ -72,11 +74,9 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { @@ -72,11 +74,9 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
72 alarmFieldColumnMap.put("originator_id", ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY); 74 alarmFieldColumnMap.put("originator_id", ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY);
73 alarmFieldColumnMap.put("originator_type", ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY); 75 alarmFieldColumnMap.put("originator_type", ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY);
74 alarmFieldColumnMap.put("originator", "originator_name"); 76 alarmFieldColumnMap.put("originator", "originator_name");
75 -  
76 - uniqueAlarmFields.addAll(new HashSet<>(alarmFieldColumnMap.values()));  
77 } 77 }
78 78
79 - public static final String SELECT_ORIGINATOR_NAME = " CASE" + 79 + private static final String SELECT_ORIGINATOR_NAME = " CASE" +
80 " WHEN a.originator_type = " + EntityType.TENANT.ordinal() + 80 " WHEN a.originator_type = " + EntityType.TENANT.ordinal() +
81 " THEN (select title from tenant where id = a.originator_id)" + 81 " THEN (select title from tenant where id = a.originator_id)" +
82 " WHEN a.originator_type = " + EntityType.CUSTOMER.ordinal() + 82 " WHEN a.originator_type = " + EntityType.CUSTOMER.ordinal() +
@@ -93,7 +93,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { @@ -93,7 +93,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
93 " THEN (select name from entity_view where id = a.originator_id)" + 93 " THEN (select name from entity_view where id = a.originator_id)" +
94 " END as originator_name"; 94 " END as originator_name";
95 95
96 - public static final String FIELDS_SELECTION = "select a.id as id," + 96 + private static final String FIELDS_SELECTION = "select a.id as id," +
97 " a.created_time as created_time," + 97 " a.created_time as created_time," +
98 " a.ack_ts as ack_ts," + 98 " a.ack_ts as ack_ts," +
99 " a.clear_ts as clear_ts," + 99 " a.clear_ts as clear_ts," +
@@ -109,130 +109,136 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { @@ -109,130 +109,136 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
109 " a.propagate_relation_types as propagate_relation_types, " + 109 " a.propagate_relation_types as propagate_relation_types, " +
110 " a.type as type," + SELECT_ORIGINATOR_NAME + ", "; 110 " a.type as type," + SELECT_ORIGINATOR_NAME + ", ";
111 111
112 - public static final String JOIN_RELATIONS = "left join relation r on r.relation_type_group = 'ALARM' and r.relation_type = 'ANY' and a.id = r.to_id and r.from_id in (:entity_ids)"; 112 + private static final String JOIN_RELATIONS = "left join relation r on r.relation_type_group = 'ALARM' and r.relation_type = 'ANY' and a.id = r.to_id and r.from_id in (:entity_ids)";
113 113
114 - @Autowired  
115 - protected NamedParameterJdbcTemplate jdbcTemplate; 114 + protected final NamedParameterJdbcTemplate jdbcTemplate;
  115 + private final TransactionTemplate transactionTemplate;
116 116
  117 + public DefaultAlarmQueryRepository(NamedParameterJdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate) {
  118 + this.jdbcTemplate = jdbcTemplate;
  119 + this.transactionTemplate = transactionTemplate;
  120 + }
117 121
118 @Override 122 @Override
119 public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, 123 public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId,
120 AlarmDataQuery query, Collection<EntityId> orderedEntityIds) { 124 AlarmDataQuery query, Collection<EntityId> orderedEntityIds) {
121 - AlarmDataPageLink pageLink = query.getPageLink();  
122 - QueryContext ctx = new QueryContext();  
123 - ctx.addUuidListParameter("entity_ids", orderedEntityIds.stream().map(EntityId::getId).collect(Collectors.toList())); 125 + return transactionTemplate.execute(status -> {
  126 + AlarmDataPageLink pageLink = query.getPageLink();
  127 + QueryContext ctx = new QueryContext();
  128 + ctx.addUuidListParameter("entity_ids", orderedEntityIds.stream().map(EntityId::getId).collect(Collectors.toList()));
124 129
125 - StringBuilder selectPart = new StringBuilder(FIELDS_SELECTION);  
126 - StringBuilder fromPart = new StringBuilder(" from alarm a ");  
127 - StringBuilder wherePart = new StringBuilder(" where ");  
128 - StringBuilder sortPart = new StringBuilder(" order by ");  
129 - boolean addAnd = false;  
130 - if (pageLink.isSearchPropagatedAlarms()) {  
131 - selectPart.append(" CASE WHEN r.from_id IS NULL THEN a.originator_id ELSE r.from_id END as entity_id ");  
132 - fromPart.append(JOIN_RELATIONS);  
133 - wherePart.append(buildPermissionsQuery(tenantId, customerId, ctx));  
134 - addAnd = true;  
135 - } else {  
136 - selectPart.append(" a.originator_id as entity_id ");  
137 - }  
138 - EntityDataSortOrder sortOrder = pageLink.getSortOrder();  
139 - if (sortOrder != null && sortOrder.getKey().getType().equals(EntityKeyType.ALARM_FIELD)) {  
140 - String sortOrderKey = sortOrder.getKey().getKey();  
141 - sortPart.append(alarmFieldColumnMap.getOrDefault(sortOrderKey, sortOrderKey))  
142 - .append(" ").append(sortOrder.getDirection().name()); 130 + StringBuilder selectPart = new StringBuilder(FIELDS_SELECTION);
  131 + StringBuilder fromPart = new StringBuilder(" from alarm a ");
  132 + StringBuilder wherePart = new StringBuilder(" where ");
  133 + StringBuilder sortPart = new StringBuilder(" order by ");
  134 + boolean addAnd = false;
143 if (pageLink.isSearchPropagatedAlarms()) { 135 if (pageLink.isSearchPropagatedAlarms()) {
144 - wherePart.append(" and (a.originator_id in (:entity_ids) or r.from_id IS NOT NULL)");  
145 - } else {  
146 - addAndIfNeeded(wherePart, addAnd); 136 + selectPart.append(" CASE WHEN r.from_id IS NULL THEN a.originator_id ELSE r.from_id END as entity_id ");
  137 + fromPart.append(JOIN_RELATIONS);
  138 + wherePart.append(buildPermissionsQuery(tenantId, customerId, ctx));
147 addAnd = true; 139 addAnd = true;
148 - wherePart.append(" a.originator_id in (:entity_ids)"); 140 + } else {
  141 + selectPart.append(" a.originator_id as entity_id ");
149 } 142 }
150 - } else {  
151 - fromPart.append(" left join (select * from (VALUES");  
152 - int entityIdIdx = 0;  
153 - int lastEntityIdIdx = orderedEntityIds.size() - 1;  
154 - for (EntityId entityId : orderedEntityIds) {  
155 - fromPart.append("(uuid('").append(entityId.getId().toString()).append("'), ").append(entityIdIdx).append(")");  
156 - if (entityIdIdx != lastEntityIdIdx) {  
157 - fromPart.append(","); 143 + EntityDataSortOrder sortOrder = pageLink.getSortOrder();
  144 + if (sortOrder != null && sortOrder.getKey().getType().equals(EntityKeyType.ALARM_FIELD)) {
  145 + String sortOrderKey = sortOrder.getKey().getKey();
  146 + sortPart.append(alarmFieldColumnMap.getOrDefault(sortOrderKey, sortOrderKey))
  147 + .append(" ").append(sortOrder.getDirection().name());
  148 + if (pageLink.isSearchPropagatedAlarms()) {
  149 + wherePart.append(" and (a.originator_id in (:entity_ids) or r.from_id IS NOT NULL)");
158 } else { 150 } else {
159 - fromPart.append(")"); 151 + addAndIfNeeded(wherePart, addAnd);
  152 + addAnd = true;
  153 + wherePart.append(" a.originator_id in (:entity_ids)");
160 } 154 }
161 - entityIdIdx++;  
162 - }  
163 - fromPart.append(" as e(id, priority)) e ");  
164 - if (pageLink.isSearchPropagatedAlarms()) {  
165 - fromPart.append("on (r.from_id IS NULL and a.originator_id = e.id) or (r.from_id IS NOT NULL and r.from_id = e.id)");  
166 } else { 155 } else {
167 - fromPart.append("on a.originator_id = e.id"); 156 + fromPart.append(" left join (select * from (VALUES");
  157 + int entityIdIdx = 0;
  158 + int lastEntityIdIdx = orderedEntityIds.size() - 1;
  159 + for (EntityId entityId : orderedEntityIds) {
  160 + fromPart.append("(uuid('").append(entityId.getId().toString()).append("'), ").append(entityIdIdx).append(")");
  161 + if (entityIdIdx != lastEntityIdIdx) {
  162 + fromPart.append(",");
  163 + } else {
  164 + fromPart.append(")");
  165 + }
  166 + entityIdIdx++;
  167 + }
  168 + fromPart.append(" as e(id, priority)) e ");
  169 + if (pageLink.isSearchPropagatedAlarms()) {
  170 + fromPart.append("on (r.from_id IS NULL and a.originator_id = e.id) or (r.from_id IS NOT NULL and r.from_id = e.id)");
  171 + } else {
  172 + fromPart.append("on a.originator_id = e.id");
  173 + }
  174 + sortPart.append("e.priority");
168 } 175 }
169 - sortPart.append("e.priority");  
170 - }  
171 -  
172 - long startTs;  
173 - long endTs;  
174 - if (pageLink.getTimeWindow() > 0) {  
175 - endTs = System.currentTimeMillis();  
176 - startTs = endTs - pageLink.getTimeWindow();  
177 - } else {  
178 - startTs = pageLink.getStartTs();  
179 - endTs = pageLink.getEndTs();  
180 - }  
181 176
182 - if (startTs > 0) {  
183 - addAndIfNeeded(wherePart, addAnd);  
184 - addAnd = true;  
185 - ctx.addLongParameter("startTime", startTs);  
186 - wherePart.append("a.created_time >= :startTime");  
187 - } 177 + long startTs;
  178 + long endTs;
  179 + if (pageLink.getTimeWindow() > 0) {
  180 + endTs = System.currentTimeMillis();
  181 + startTs = endTs - pageLink.getTimeWindow();
  182 + } else {
  183 + startTs = pageLink.getStartTs();
  184 + endTs = pageLink.getEndTs();
  185 + }
188 186
189 - if (endTs > 0) {  
190 - addAndIfNeeded(wherePart, addAnd);  
191 - addAnd = true;  
192 - ctx.addLongParameter("endTime", endTs);  
193 - wherePart.append("a.created_time <= :endTime");  
194 - } 187 + if (startTs > 0) {
  188 + addAndIfNeeded(wherePart, addAnd);
  189 + addAnd = true;
  190 + ctx.addLongParameter("startTime", startTs);
  191 + wherePart.append("a.created_time >= :startTime");
  192 + }
195 193
196 - if (pageLink.getTypeList() != null && !pageLink.getTypeList().isEmpty()) {  
197 - addAndIfNeeded(wherePart, addAnd);  
198 - addAnd = true;  
199 - ctx.addStringListParameter("alarmTypes", pageLink.getTypeList());  
200 - wherePart.append("a.type in (:alarmTypes)");  
201 - } 194 + if (endTs > 0) {
  195 + addAndIfNeeded(wherePart, addAnd);
  196 + addAnd = true;
  197 + ctx.addLongParameter("endTime", endTs);
  198 + wherePart.append("a.created_time <= :endTime");
  199 + }
202 200
203 - if (pageLink.getSeverityList() != null && !pageLink.getSeverityList().isEmpty()) {  
204 - addAndIfNeeded(wherePart, addAnd);  
205 - addAnd = true;  
206 - ctx.addStringListParameter("alarmSeverities", pageLink.getSeverityList().stream().map(AlarmSeverity::name).collect(Collectors.toList()));  
207 - wherePart.append("a.severity in (:alarmSeverities)");  
208 - } 201 + if (pageLink.getTypeList() != null && !pageLink.getTypeList().isEmpty()) {
  202 + addAndIfNeeded(wherePart, addAnd);
  203 + addAnd = true;
  204 + ctx.addStringListParameter("alarmTypes", pageLink.getTypeList());
  205 + wherePart.append("a.type in (:alarmTypes)");
  206 + }
209 207
210 - if (pageLink.getStatusList() != null && !pageLink.getStatusList().isEmpty()) {  
211 - Set<AlarmStatus> statusSet = toStatusSet(pageLink.getStatusList());  
212 - if (!statusSet.isEmpty()) { 208 + if (pageLink.getSeverityList() != null && !pageLink.getSeverityList().isEmpty()) {
213 addAndIfNeeded(wherePart, addAnd); 209 addAndIfNeeded(wherePart, addAnd);
214 addAnd = true; 210 addAnd = true;
215 - ctx.addStringListParameter("alarmStatuses", statusSet.stream().map(AlarmStatus::name).collect(Collectors.toList()));  
216 - wherePart.append(" a.status in (:alarmStatuses)"); 211 + ctx.addStringListParameter("alarmSeverities", pageLink.getSeverityList().stream().map(AlarmSeverity::name).collect(Collectors.toList()));
  212 + wherePart.append("a.severity in (:alarmSeverities)");
217 } 213 }
218 - }  
219 214
220 - String textSearchQuery = buildTextSearchQuery(ctx, query.getAlarmFields(), pageLink.getTextSearch());  
221 - String mainQuery = selectPart.toString() + fromPart.toString() + wherePart.toString();  
222 - if (!textSearchQuery.isEmpty()) {  
223 - mainQuery = String.format("select * from (%s) a WHERE %s", mainQuery, textSearchQuery);  
224 - }  
225 - String countQuery = mainQuery;  
226 - int totalElements = jdbcTemplate.queryForObject(String.format("select count(*) from (%s) result", countQuery), ctx, Integer.class); 215 + if (pageLink.getStatusList() != null && !pageLink.getStatusList().isEmpty()) {
  216 + Set<AlarmStatus> statusSet = toStatusSet(pageLink.getStatusList());
  217 + if (!statusSet.isEmpty()) {
  218 + addAndIfNeeded(wherePart, addAnd);
  219 + addAnd = true;
  220 + ctx.addStringListParameter("alarmStatuses", statusSet.stream().map(AlarmStatus::name).collect(Collectors.toList()));
  221 + wherePart.append(" a.status in (:alarmStatuses)");
  222 + }
  223 + }
227 224
228 - String dataQuery = mainQuery + sortPart; 225 + String textSearchQuery = buildTextSearchQuery(ctx, query.getAlarmFields(), pageLink.getTextSearch());
  226 + String mainQuery = selectPart.toString() + fromPart.toString() + wherePart.toString();
  227 + if (!textSearchQuery.isEmpty()) {
  228 + mainQuery = String.format("select * from (%s) a WHERE %s", mainQuery, textSearchQuery);
  229 + }
  230 + String countQuery = mainQuery;
  231 + int totalElements = jdbcTemplate.queryForObject(String.format("select count(*) from (%s) result", countQuery), ctx, Integer.class);
229 232
230 - int startIndex = pageLink.getPageSize() * pageLink.getPage();  
231 - if (pageLink.getPageSize() > 0) {  
232 - dataQuery = String.format("%s limit %s offset %s", dataQuery, pageLink.getPageSize(), startIndex);  
233 - }  
234 - List<Map<String, Object>> rows = jdbcTemplate.queryForList(dataQuery, ctx);  
235 - return AlarmDataAdapter.createAlarmData(pageLink, rows, totalElements, orderedEntityIds); 233 + String dataQuery = mainQuery + sortPart;
  234 +
  235 + int startIndex = pageLink.getPageSize() * pageLink.getPage();
  236 + if (pageLink.getPageSize() > 0) {
  237 + dataQuery = String.format("%s limit %s offset %s", dataQuery, pageLink.getPageSize(), startIndex);
  238 + }
  239 + List<Map<String, Object>> rows = jdbcTemplate.queryForList(dataQuery, ctx);
  240 + return AlarmDataAdapter.createAlarmData(pageLink, rows, totalElements, orderedEntityIds);
  241 + });
236 } 242 }
237 243
238 private String buildTextSearchQuery(QueryContext ctx, List<EntityKey> selectionMapping, String searchText) { 244 private String buildTextSearchQuery(QueryContext ctx, List<EntityKey> selectionMapping, String searchText) {
@@ -20,6 +20,9 @@ import org.apache.commons.lang3.StringUtils; @@ -20,6 +20,9 @@ import org.apache.commons.lang3.StringUtils;
20 import org.springframework.beans.factory.annotation.Autowired; 20 import org.springframework.beans.factory.annotation.Autowired;
21 import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 21 import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
22 import org.springframework.stereotype.Repository; 22 import org.springframework.stereotype.Repository;
  23 +import org.springframework.transaction.TransactionStatus;
  24 +import org.springframework.transaction.support.TransactionCallback;
  25 +import org.springframework.transaction.support.TransactionTemplate;
23 import org.thingsboard.server.common.data.EntityType; 26 import org.thingsboard.server.common.data.EntityType;
24 import org.thingsboard.server.common.data.id.CustomerId; 27 import org.thingsboard.server.common.data.id.CustomerId;
25 import org.thingsboard.server.common.data.id.EntityId; 28 import org.thingsboard.server.common.data.id.EntityId;
@@ -58,37 +61,36 @@ import java.util.stream.Collectors; @@ -58,37 +61,36 @@ import java.util.stream.Collectors;
58 @Repository 61 @Repository
59 @Slf4j 62 @Slf4j
60 public class DefaultEntityQueryRepository implements EntityQueryRepository { 63 public class DefaultEntityQueryRepository implements EntityQueryRepository {
61 - //TODO: rafactoring to protect from SQL injections;  
62 private static final Map<EntityType, String> entityTableMap = new HashMap<>(); 64 private static final Map<EntityType, String> entityTableMap = new HashMap<>();
63 - public static final String SELECT_PHONE = " CASE WHEN entity.entity_type = 'TENANT' THEN (select phone from tenant where id = entity_id)" + 65 + private static final String SELECT_PHONE = " CASE WHEN entity.entity_type = 'TENANT' THEN (select phone from tenant where id = entity_id)" +
64 " WHEN entity.entity_type = 'CUSTOMER' THEN (select phone from customer where id = entity_id) END as phone"; 66 " WHEN entity.entity_type = 'CUSTOMER' THEN (select phone from customer where id = entity_id) END as phone";
65 - public static final String SELECT_ZIP = " CASE WHEN entity.entity_type = 'TENANT' THEN (select zip from tenant where id = entity_id)" + 67 + private static final String SELECT_ZIP = " CASE WHEN entity.entity_type = 'TENANT' THEN (select zip from tenant where id = entity_id)" +
66 " WHEN entity.entity_type = 'CUSTOMER' THEN (select zip from customer where id = entity_id) END as zip"; 68 " WHEN entity.entity_type = 'CUSTOMER' THEN (select zip from customer where id = entity_id) END as zip";
67 - public static final String SELECT_ADDRESS_2 = " CASE WHEN entity.entity_type = 'TENANT'" + 69 + private static final String SELECT_ADDRESS_2 = " CASE WHEN entity.entity_type = 'TENANT'" +
68 " THEN (select address2 from tenant where id = entity_id) WHEN entity.entity_type = 'CUSTOMER' " + 70 " THEN (select address2 from tenant where id = entity_id) WHEN entity.entity_type = 'CUSTOMER' " +
69 " THEN (select address2 from customer where id = entity_id) END as address2"; 71 " THEN (select address2 from customer where id = entity_id) END as address2";
70 - public static final String SELECT_ADDRESS = " CASE WHEN entity.entity_type = 'TENANT'" + 72 + private static final String SELECT_ADDRESS = " CASE WHEN entity.entity_type = 'TENANT'" +
71 " THEN (select address from tenant where id = entity_id) WHEN entity.entity_type = 'CUSTOMER' " + 73 " THEN (select address from tenant where id = entity_id) WHEN entity.entity_type = 'CUSTOMER' " +
72 " THEN (select address from customer where id = entity_id) END as address"; 74 " THEN (select address from customer where id = entity_id) END as address";
73 - public static final String SELECT_CITY = " CASE WHEN entity.entity_type = 'TENANT'" + 75 + private static final String SELECT_CITY = " CASE WHEN entity.entity_type = 'TENANT'" +
74 " THEN (select city from tenant where id = entity_id) WHEN entity.entity_type = 'CUSTOMER' " + 76 " THEN (select city from tenant where id = entity_id) WHEN entity.entity_type = 'CUSTOMER' " +
75 " THEN (select city from customer where id = entity_id) END as city"; 77 " THEN (select city from customer where id = entity_id) END as city";
76 - public static final String SELECT_STATE = " CASE WHEN entity.entity_type = 'TENANT'" + 78 + private static final String SELECT_STATE = " CASE WHEN entity.entity_type = 'TENANT'" +
77 " THEN (select state from tenant where id = entity_id) WHEN entity.entity_type = 'CUSTOMER' " + 79 " THEN (select state from tenant where id = entity_id) WHEN entity.entity_type = 'CUSTOMER' " +
78 " THEN (select state from customer where id = entity_id) END as state"; 80 " THEN (select state from customer where id = entity_id) END as state";
79 - public static final String SELECT_COUNTRY = " CASE WHEN entity.entity_type = 'TENANT'" + 81 + private static final String SELECT_COUNTRY = " CASE WHEN entity.entity_type = 'TENANT'" +
80 " THEN (select country from tenant where id = entity_id) WHEN entity.entity_type = 'CUSTOMER' " + 82 " THEN (select country from tenant where id = entity_id) WHEN entity.entity_type = 'CUSTOMER' " +
81 " THEN (select country from customer where id = entity_id) END as country"; 83 " THEN (select country from customer where id = entity_id) END as country";
82 - public static final String SELECT_TITLE = " CASE WHEN entity.entity_type = 'TENANT'" + 84 + private static final String SELECT_TITLE = " CASE WHEN entity.entity_type = 'TENANT'" +
83 " THEN (select title from tenant where id = entity_id) WHEN entity.entity_type = 'CUSTOMER' " + 85 " THEN (select title from tenant where id = entity_id) WHEN entity.entity_type = 'CUSTOMER' " +
84 " THEN (select title from customer where id = entity_id) END as title"; 86 " THEN (select title from customer where id = entity_id) END as title";
85 - public static final String SELECT_LAST_NAME = " CASE WHEN entity.entity_type = 'USER'" + 87 + private static final String SELECT_LAST_NAME = " CASE WHEN entity.entity_type = 'USER'" +
86 " THEN (select last_name from tb_user where id = entity_id) END as last_name"; 88 " THEN (select last_name from tb_user where id = entity_id) END as last_name";
87 - public static final String SELECT_FIRST_NAME = " CASE WHEN entity.entity_type = 'USER'" + 89 + private static final String SELECT_FIRST_NAME = " CASE WHEN entity.entity_type = 'USER'" +
88 " THEN (select first_name from tb_user where id = entity_id) END as first_name"; 90 " THEN (select first_name from tb_user where id = entity_id) END as first_name";
89 - public static final String SELECT_REGION = " CASE WHEN entity.entity_type = 'TENANT'" + 91 + private static final String SELECT_REGION = " CASE WHEN entity.entity_type = 'TENANT'" +
90 " THEN (select region from tenant where id = entity_id) END as region"; 92 " THEN (select region from tenant where id = entity_id) END as region";
91 - public static final String SELECT_EMAIL = " CASE" + 93 + private static final String SELECT_EMAIL = " CASE" +
92 " WHEN entity.entity_type = 'TENANT'" + 94 " WHEN entity.entity_type = 'TENANT'" +
93 " THEN (select email from tenant where id = entity_id)" + 95 " THEN (select email from tenant where id = entity_id)" +
94 " WHEN entity.entity_type = 'CUSTOMER' " + 96 " WHEN entity.entity_type = 'CUSTOMER' " +
@@ -96,7 +98,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -96,7 +98,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
96 " WHEN entity.entity_type = 'USER'" + 98 " WHEN entity.entity_type = 'USER'" +
97 " THEN (select email from tb_user where id = entity_id)" + 99 " THEN (select email from tb_user where id = entity_id)" +
98 " END as email"; 100 " END as email";
99 - public static final String SELECT_CUSTOMER_ID = "CASE" + 101 + private static final String SELECT_CUSTOMER_ID = "CASE" +
100 " WHEN entity.entity_type = 'TENANT'" + 102 " WHEN entity.entity_type = 'TENANT'" +
101 " THEN UUID('" + TenantId.NULL_UUID + "')" + 103 " THEN UUID('" + TenantId.NULL_UUID + "')" +
102 " WHEN entity.entity_type = 'CUSTOMER' THEN entity_id" + 104 " WHEN entity.entity_type = 'CUSTOMER' THEN entity_id" +
@@ -112,7 +114,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -112,7 +114,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
112 " WHEN entity.entity_type = 'ENTITY_VIEW'" + 114 " WHEN entity.entity_type = 'ENTITY_VIEW'" +
113 " THEN (select customer_id from entity_view where id = entity_id)" + 115 " THEN (select customer_id from entity_view where id = entity_id)" +
114 " END as customer_id"; 116 " END as customer_id";
115 - public static final String SELECT_TENANT_ID = "SELECT CASE" + 117 + private static final String SELECT_TENANT_ID = "SELECT CASE" +
116 " WHEN entity.entity_type = 'TENANT' THEN entity_id" + 118 " WHEN entity.entity_type = 'TENANT' THEN entity_id" +
117 " WHEN entity.entity_type = 'CUSTOMER'" + 119 " WHEN entity.entity_type = 'CUSTOMER'" +
118 " THEN (select tenant_id from customer where id = entity_id)" + 120 " THEN (select tenant_id from customer where id = entity_id)" +
@@ -127,7 +129,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -127,7 +129,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
127 " WHEN entity.entity_type = 'ENTITY_VIEW'" + 129 " WHEN entity.entity_type = 'ENTITY_VIEW'" +
128 " THEN (select tenant_id from entity_view where id = entity_id)" + 130 " THEN (select tenant_id from entity_view where id = entity_id)" +
129 " END as tenant_id"; 131 " END as tenant_id";
130 - public static final String SELECT_CREATED_TIME = " CASE" + 132 + private static final String SELECT_CREATED_TIME = " CASE" +
131 " WHEN entity.entity_type = 'TENANT'" + 133 " WHEN entity.entity_type = 'TENANT'" +
132 " THEN (select created_time from tenant where id = entity_id)" + 134 " THEN (select created_time from tenant where id = entity_id)" +
133 " WHEN entity.entity_type = 'CUSTOMER' " + 135 " WHEN entity.entity_type = 'CUSTOMER' " +
@@ -143,7 +145,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -143,7 +145,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
143 " WHEN entity.entity_type = 'ENTITY_VIEW'" + 145 " WHEN entity.entity_type = 'ENTITY_VIEW'" +
144 " THEN (select created_time from entity_view where id = entity_id)" + 146 " THEN (select created_time from entity_view where id = entity_id)" +
145 " END as created_time"; 147 " END as created_time";
146 - public static final String SELECT_NAME = " CASE" + 148 + private static final String SELECT_NAME = " CASE" +
147 " WHEN entity.entity_type = 'TENANT'" + 149 " WHEN entity.entity_type = 'TENANT'" +
148 " THEN (select title from tenant where id = entity_id)" + 150 " THEN (select title from tenant where id = entity_id)" +
149 " WHEN entity.entity_type = 'CUSTOMER' " + 151 " WHEN entity.entity_type = 'CUSTOMER' " +
@@ -159,7 +161,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -159,7 +161,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
159 " WHEN entity.entity_type = 'ENTITY_VIEW'" + 161 " WHEN entity.entity_type = 'ENTITY_VIEW'" +
160 " THEN (select name from entity_view where id = entity_id)" + 162 " THEN (select name from entity_view where id = entity_id)" +
161 " END as name"; 163 " END as name";
162 - public static final String SELECT_TYPE = " CASE" + 164 + private static final String SELECT_TYPE = " CASE" +
163 " WHEN entity.entity_type = 'USER'" + 165 " WHEN entity.entity_type = 'USER'" +
164 " THEN (select authority from tb_user where id = entity_id)" + 166 " THEN (select authority from tb_user where id = entity_id)" +
165 " WHEN entity.entity_type = 'ASSET'" + 167 " WHEN entity.entity_type = 'ASSET'" +
@@ -169,7 +171,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -169,7 +171,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
169 " WHEN entity.entity_type = 'ENTITY_VIEW'" + 171 " WHEN entity.entity_type = 'ENTITY_VIEW'" +
170 " THEN (select type from entity_view where id = entity_id)" + 172 " THEN (select type from entity_view where id = entity_id)" +
171 " ELSE entity.entity_type END as type"; 173 " ELSE entity.entity_type END as type";
172 - public static final String SELECT_LABEL = " CASE" + 174 + private static final String SELECT_LABEL = " CASE" +
173 " WHEN entity.entity_type = 'TENANT'" + 175 " WHEN entity.entity_type = 'TENANT'" +
174 " THEN (select title from tenant where id = entity_id)" + 176 " THEN (select title from tenant where id = entity_id)" +
175 " WHEN entity.entity_type = 'CUSTOMER' " + 177 " WHEN entity.entity_type = 'CUSTOMER' " +
@@ -196,7 +198,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -196,7 +198,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
196 entityTableMap.put(EntityType.TENANT, "tenant"); 198 entityTableMap.put(EntityType.TENANT, "tenant");
197 } 199 }
198 200
199 - public static final String HIERARCHICAL_QUERY_TEMPLATE = " FROM (WITH RECURSIVE related_entities(from_id, from_type, to_id, to_type, relation_type, lvl) AS (" + 201 + private static final String HIERARCHICAL_QUERY_TEMPLATE = " FROM (WITH RECURSIVE related_entities(from_id, from_type, to_id, to_type, relation_type, lvl) AS (" +
200 " SELECT from_id, from_type, to_id, to_type, relation_type, 1 as lvl" + 202 " SELECT from_id, from_type, to_id, to_type, relation_type, 1 as lvl" +
201 " FROM relation" + 203 " FROM relation" +
202 " WHERE $in_id = :relation_root_id and $in_type = :relation_root_type and relation_type_group = 'COMMON'" + 204 " WHERE $in_id = :relation_root_id and $in_type = :relation_root_type and relation_type_group = 'COMMON'" +
@@ -209,11 +211,16 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -209,11 +211,16 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
209 " SELECT re.$out_id entity_id, re.$out_type entity_type, re.lvl lvl" + 211 " SELECT re.$out_id entity_id, re.$out_type entity_type, re.lvl lvl" +
210 " from related_entities re" + 212 " from related_entities re" +
211 " %s ) entity"; 213 " %s ) entity";
212 - public static final String HIERARCHICAL_TO_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE.replace("$in", "to").replace("$out", "from");  
213 - public static final String HIERARCHICAL_FROM_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE.replace("$in", "from").replace("$out", "to"); 214 + private static final String HIERARCHICAL_TO_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE.replace("$in", "to").replace("$out", "from");
  215 + private static final String HIERARCHICAL_FROM_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE.replace("$in", "from").replace("$out", "to");
214 216
215 - @Autowired  
216 - protected NamedParameterJdbcTemplate jdbcTemplate; 217 + private final NamedParameterJdbcTemplate jdbcTemplate;
  218 + private final TransactionTemplate transactionTemplate;
  219 +
  220 + public DefaultEntityQueryRepository(NamedParameterJdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate) {
  221 + this.jdbcTemplate = jdbcTemplate;
  222 + this.transactionTemplate = transactionTemplate;
  223 + }
217 224
218 @Override 225 @Override
219 public long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query) { 226 public long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query) {
@@ -223,89 +230,91 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -223,89 +230,91 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
223 ctx.append(addEntityTableQuery(ctx, query.getEntityFilter(), entityType)); 230 ctx.append(addEntityTableQuery(ctx, query.getEntityFilter(), entityType));
224 ctx.append(" e where "); 231 ctx.append(" e where ");
225 ctx.append(buildEntityWhere(ctx, tenantId, customerId, query.getEntityFilter(), Collections.emptyList(), entityType)); 232 ctx.append(buildEntityWhere(ctx, tenantId, customerId, query.getEntityFilter(), Collections.emptyList(), entityType));
226 - return jdbcTemplate.queryForObject(ctx.getQuery(), ctx, Long.class); 233 + return transactionTemplate.execute(status -> jdbcTemplate.queryForObject(ctx.getQuery(), ctx, Long.class));
227 } 234 }
228 235
229 @Override 236 @Override
230 public PageData<EntityData> findEntityDataByQuery(TenantId tenantId, CustomerId customerId, EntityDataQuery query) { 237 public PageData<EntityData> findEntityDataByQuery(TenantId tenantId, CustomerId customerId, EntityDataQuery query) {
231 - QueryContext ctx = new QueryContext();  
232 - EntityType entityType = resolveEntityType(query.getEntityFilter());  
233 - EntityDataPageLink pageLink = query.getPageLink();  
234 -  
235 - List<EntityKeyMapping> mappings = EntityKeyMapping.prepareKeyMapping(query);  
236 -  
237 - List<EntityKeyMapping> selectionMapping = mappings.stream().filter(EntityKeyMapping::isSelection)  
238 - .collect(Collectors.toList());  
239 - List<EntityKeyMapping> entityFieldsSelectionMapping = selectionMapping.stream().filter(mapping -> !mapping.isLatest())  
240 - .collect(Collectors.toList());  
241 - List<EntityKeyMapping> latestSelectionMapping = selectionMapping.stream().filter(EntityKeyMapping::isLatest)  
242 - .collect(Collectors.toList());  
243 -  
244 - List<EntityKeyMapping> filterMapping = mappings.stream().filter(EntityKeyMapping::hasFilter)  
245 - .collect(Collectors.toList());  
246 - List<EntityKeyMapping> entityFieldsFiltersMapping = filterMapping.stream().filter(mapping -> !mapping.isLatest())  
247 - .collect(Collectors.toList());  
248 - List<EntityKeyMapping> latestFiltersMapping = filterMapping.stream().filter(EntityKeyMapping::isLatest)  
249 - .collect(Collectors.toList());  
250 -  
251 - List<EntityKeyMapping> allLatestMappings = mappings.stream().filter(EntityKeyMapping::isLatest)  
252 - .collect(Collectors.toList());  
253 -  
254 -  
255 - String entityWhereClause = this.buildEntityWhere(ctx, tenantId, customerId, query.getEntityFilter(), entityFieldsFiltersMapping, entityType);  
256 - String latestJoins = EntityKeyMapping.buildLatestJoins(ctx, query.getEntityFilter(), entityType, allLatestMappings);  
257 - String whereClause = this.buildWhere(ctx, latestFiltersMapping, query.getEntityFilter().getType(), entityType);  
258 - String textSearchQuery = this.buildTextSearchQuery(ctx, selectionMapping, pageLink.getTextSearch());  
259 - String entityFieldsSelection = EntityKeyMapping.buildSelections(entityFieldsSelectionMapping, query.getEntityFilter().getType(), entityType);  
260 - String entityTypeStr;  
261 - if (query.getEntityFilter().getType().equals(EntityFilterType.RELATIONS_QUERY)) {  
262 - entityTypeStr = "e.entity_type";  
263 - } else {  
264 - entityTypeStr = "'" + entityType.name() + "'";  
265 - }  
266 - if (!StringUtils.isEmpty(entityFieldsSelection)) {  
267 - entityFieldsSelection = String.format("e.id id, %s entity_type, %s", entityTypeStr, entityFieldsSelection);  
268 - } else {  
269 - entityFieldsSelection = String.format("e.id id, %s entity_type", entityTypeStr);  
270 - }  
271 - String latestSelection = EntityKeyMapping.buildSelections(latestSelectionMapping, query.getEntityFilter().getType(), entityType);  
272 - String topSelection = "entities.*";  
273 - if (!StringUtils.isEmpty(latestSelection)) {  
274 - topSelection = topSelection + ", " + latestSelection;  
275 - } 238 + return transactionTemplate.execute(status -> {
  239 + QueryContext ctx = new QueryContext();
  240 + EntityType entityType = resolveEntityType(query.getEntityFilter());
  241 + EntityDataPageLink pageLink = query.getPageLink();
  242 +
  243 + List<EntityKeyMapping> mappings = EntityKeyMapping.prepareKeyMapping(query);
  244 +
  245 + List<EntityKeyMapping> selectionMapping = mappings.stream().filter(EntityKeyMapping::isSelection)
  246 + .collect(Collectors.toList());
  247 + List<EntityKeyMapping> entityFieldsSelectionMapping = selectionMapping.stream().filter(mapping -> !mapping.isLatest())
  248 + .collect(Collectors.toList());
  249 + List<EntityKeyMapping> latestSelectionMapping = selectionMapping.stream().filter(EntityKeyMapping::isLatest)
  250 + .collect(Collectors.toList());
  251 +
  252 + List<EntityKeyMapping> filterMapping = mappings.stream().filter(EntityKeyMapping::hasFilter)
  253 + .collect(Collectors.toList());
  254 + List<EntityKeyMapping> entityFieldsFiltersMapping = filterMapping.stream().filter(mapping -> !mapping.isLatest())
  255 + .collect(Collectors.toList());
  256 + List<EntityKeyMapping> latestFiltersMapping = filterMapping.stream().filter(EntityKeyMapping::isLatest)
  257 + .collect(Collectors.toList());
  258 +
  259 + List<EntityKeyMapping> allLatestMappings = mappings.stream().filter(EntityKeyMapping::isLatest)
  260 + .collect(Collectors.toList());
  261 +
  262 +
  263 + String entityWhereClause = DefaultEntityQueryRepository.this.buildEntityWhere(ctx, tenantId, customerId, query.getEntityFilter(), entityFieldsFiltersMapping, entityType);
  264 + String latestJoins = EntityKeyMapping.buildLatestJoins(ctx, query.getEntityFilter(), entityType, allLatestMappings);
  265 + String whereClause = DefaultEntityQueryRepository.this.buildWhere(ctx, latestFiltersMapping, query.getEntityFilter().getType(), entityType);
  266 + String textSearchQuery = DefaultEntityQueryRepository.this.buildTextSearchQuery(ctx, selectionMapping, pageLink.getTextSearch());
  267 + String entityFieldsSelection = EntityKeyMapping.buildSelections(entityFieldsSelectionMapping, query.getEntityFilter().getType(), entityType);
  268 + String entityTypeStr;
  269 + if (query.getEntityFilter().getType().equals(EntityFilterType.RELATIONS_QUERY)) {
  270 + entityTypeStr = "e.entity_type";
  271 + } else {
  272 + entityTypeStr = "'" + entityType.name() + "'";
  273 + }
  274 + if (!StringUtils.isEmpty(entityFieldsSelection)) {
  275 + entityFieldsSelection = String.format("e.id id, %s entity_type, %s", entityTypeStr, entityFieldsSelection);
  276 + } else {
  277 + entityFieldsSelection = String.format("e.id id, %s entity_type", entityTypeStr);
  278 + }
  279 + String latestSelection = EntityKeyMapping.buildSelections(latestSelectionMapping, query.getEntityFilter().getType(), entityType);
  280 + String topSelection = "entities.*";
  281 + if (!StringUtils.isEmpty(latestSelection)) {
  282 + topSelection = topSelection + ", " + latestSelection;
  283 + }
276 284
277 - String fromClause = String.format("from (select %s from (select %s from %s e where %s) entities %s %s) result %s",  
278 - topSelection,  
279 - entityFieldsSelection,  
280 - addEntityTableQuery(ctx, query.getEntityFilter(), entityType),  
281 - entityWhereClause,  
282 - latestJoins,  
283 - whereClause,  
284 - textSearchQuery);  
285 -  
286 - int totalElements = jdbcTemplate.queryForObject(String.format("select count(*) %s", fromClause), ctx, Integer.class);  
287 -  
288 - String dataQuery = String.format("select * %s", fromClause);  
289 -  
290 - EntityDataSortOrder sortOrder = pageLink.getSortOrder();  
291 - if (sortOrder != null) {  
292 - Optional<EntityKeyMapping> sortOrderMappingOpt = mappings.stream().filter(EntityKeyMapping::isSortOrder).findFirst();  
293 - if (sortOrderMappingOpt.isPresent()) {  
294 - EntityKeyMapping sortOrderMapping = sortOrderMappingOpt.get();  
295 - dataQuery = String.format("%s order by %s", dataQuery, sortOrderMapping.getValueAlias());  
296 - if (sortOrder.getDirection() == EntityDataSortOrder.Direction.ASC) {  
297 - dataQuery += " asc";  
298 - } else {  
299 - dataQuery += " desc"; 285 + String fromClause = String.format("from (select %s from (select %s from %s e where %s) entities %s %s) result %s",
  286 + topSelection,
  287 + entityFieldsSelection,
  288 + addEntityTableQuery(ctx, query.getEntityFilter(), entityType),
  289 + entityWhereClause,
  290 + latestJoins,
  291 + whereClause,
  292 + textSearchQuery);
  293 +
  294 + int totalElements = jdbcTemplate.queryForObject(String.format("select count(*) %s", fromClause), ctx, Integer.class);
  295 +
  296 + String dataQuery = String.format("select * %s", fromClause);
  297 +
  298 + EntityDataSortOrder sortOrder = pageLink.getSortOrder();
  299 + if (sortOrder != null) {
  300 + Optional<EntityKeyMapping> sortOrderMappingOpt = mappings.stream().filter(EntityKeyMapping::isSortOrder).findFirst();
  301 + if (sortOrderMappingOpt.isPresent()) {
  302 + EntityKeyMapping sortOrderMapping = sortOrderMappingOpt.get();
  303 + dataQuery = String.format("%s order by %s", dataQuery, sortOrderMapping.getValueAlias());
  304 + if (sortOrder.getDirection() == EntityDataSortOrder.Direction.ASC) {
  305 + dataQuery += " asc";
  306 + } else {
  307 + dataQuery += " desc";
  308 + }
300 } 309 }
301 } 310 }
302 - }  
303 - int startIndex = pageLink.getPageSize() * pageLink.getPage();  
304 - if (pageLink.getPageSize() > 0) {  
305 - dataQuery = String.format("%s limit %s offset %s", dataQuery, pageLink.getPageSize(), startIndex);  
306 - }  
307 - List<Map<String, Object>> rows = jdbcTemplate.queryForList(dataQuery, ctx);  
308 - return EntityDataAdapter.createEntityData(pageLink, selectionMapping, rows, totalElements); 311 + int startIndex = pageLink.getPageSize() * pageLink.getPage();
  312 + if (pageLink.getPageSize() > 0) {
  313 + dataQuery = String.format("%s limit %s offset %s", dataQuery, pageLink.getPageSize(), startIndex);
  314 + }
  315 + List<Map<String, Object>> rows = jdbcTemplate.queryForList(dataQuery, ctx);
  316 + return EntityDataAdapter.createEntityData(pageLink, selectionMapping, rows, totalElements);
  317 + });
309 } 318 }
310 319
311 private String buildEntityWhere(QueryContext ctx, 320 private String buildEntityWhere(QueryContext ctx,
@@ -16,7 +16,9 @@ @@ -16,7 +16,9 @@
16 16
17 CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type ON alarm(originator_id, type, start_ts DESC); 17 CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_type ON alarm(originator_id, type, start_ts DESC);
18 18
19 -CREATE INDEX IF NOT EXISTS idx_alarm_originator_alarm_time ON alarm(originator_id, created_time DESC); 19 +CREATE INDEX IF NOT EXISTS idx_alarm_originator_created_time ON alarm(originator_id, created_time DESC);
  20 +
  21 +CREATE INDEX IF NOT EXISTS idx_alarm_tenant_created_time ON alarm(tenant_id, created_time DESC);
20 22
21 CREATE INDEX IF NOT EXISTS idx_event_type_entity_id ON event(tenant_id, event_type, entity_type, entity_id); 23 CREATE INDEX IF NOT EXISTS idx_event_type_entity_id ON event(tenant_id, event_type, entity_type, entity_id);
22 24
@@ -119,7 +119,7 @@ @@ -119,7 +119,7 @@
119 </mat-cell> 119 </mat-cell>
120 </ng-container> 120 </ng-container>
121 <mat-header-row [ngClass]="{'mat-row-select': enableSelection}" *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row> 121 <mat-header-row [ngClass]="{'mat-row-select': enableSelection}" *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row>
122 - <mat-row [ngClass]="{'mat-row-select': enableSelection, 122 + <mat-row [fxShow]="!alarmsDatasource.dataLoading" [ngClass]="{'mat-row-select': enableSelection,
123 'mat-selected': alarmsDatasource.isSelected(alarm), 123 'mat-selected': alarmsDatasource.isSelected(alarm),
124 'tb-current-entity': alarmsDatasource.isCurrentAlarm(alarm)}" 124 'tb-current-entity': alarmsDatasource.isCurrentAlarm(alarm)}"
125 *matRowDef="let alarm; columns: displayedColumns;" 125 *matRowDef="let alarm; columns: displayedColumns;"
@@ -368,7 +368,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -368,7 +368,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
368 dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label); 368 dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label);
369 dataKey.def = 'def' + this.columns.length; 369 dataKey.def = 'def' + this.columns.length;
370 const keySettings: TableWidgetDataKeySettings = dataKey.settings; 370 const keySettings: TableWidgetDataKeySettings = dataKey.settings;
371 - 371 + if (dataKey.type === DataKeyType.alarm && !isDefined(keySettings.columnWidth)) {
  372 + const alarmField = alarmFields[dataKey.name];
  373 + if (alarmField && alarmField.time) {
  374 + keySettings.columnWidth = '120px';
  375 + }
  376 + }
372 this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings); 377 this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings);
373 this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, alarm, ctx'); 378 this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, alarm, ctx');
374 this.columnWidth[dataKey.def] = getColumnWidth(keySettings); 379 this.columnWidth[dataKey.def] = getColumnWidth(keySettings);
@@ -863,7 +868,7 @@ class AlarmsDatasource implements DataSource<AlarmDataInfo> { @@ -863,7 +868,7 @@ class AlarmsDatasource implements DataSource<AlarmDataInfo> {
863 868
864 loadAlarms(pageLink: AlarmDataPageLink, sortOrderLabel: string, keyFilters: KeyFilter[]) { 869 loadAlarms(pageLink: AlarmDataPageLink, sortOrderLabel: string, keyFilters: KeyFilter[]) {
865 this.dataLoading = true; 870 this.dataLoading = true;
866 - this.clear(); 871 + // this.clear();
867 this.appliedPageLink = pageLink; 872 this.appliedPageLink = pageLink;
868 this.appliedSortOrderLabel = sortOrderLabel; 873 this.appliedSortOrderLabel = sortOrderLabel;
869 this.subscription.subscribeForAlarms(pageLink, keyFilters); 874 this.subscription.subscribeForAlarms(pageLink, keyFilters);
@@ -82,7 +82,7 @@ @@ -82,7 +82,7 @@
82 </mat-cell> 82 </mat-cell>
83 </ng-container> 83 </ng-container>
84 <mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row> 84 <mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row>
85 - <mat-row [ngClass]="{'tb-current-entity': entityDatasource.isCurrentEntity(entity)}" 85 + <mat-row [fxShow]="!entityDatasource.dataLoading" [ngClass]="{'tb-current-entity': entityDatasource.isCurrentEntity(entity)}"
86 *matRowDef="let entity; columns: displayedColumns;" 86 *matRowDef="let entity; columns: displayedColumns;"
87 (click)="onRowClick($event, entity)" (dblclick)="onRowClick($event, entity, true)"></mat-row> 87 (click)="onRowClick($event, entity)" (dblclick)="onRowClick($event, entity, true)"></mat-row>
88 </table> 88 </table>
@@ -87,6 +87,7 @@ import { @@ -87,6 +87,7 @@ import {
87 import { sortItems } from '@shared/models/page/page-link'; 87 import { sortItems } from '@shared/models/page/page-link';
88 import { entityFields } from '@shared/models/entity.models'; 88 import { entityFields } from '@shared/models/entity.models';
89 import { DatePipe } from '@angular/common'; 89 import { DatePipe } from '@angular/common';
  90 +import { alarmFields } from '@shared/models/alarm.models';
90 91
91 interface EntitiesTableWidgetSettings extends TableWidgetSettings { 92 interface EntitiesTableWidgetSettings extends TableWidgetSettings {
92 entitiesTitle: string; 93 entitiesTitle: string;
@@ -348,6 +349,13 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni @@ -348,6 +349,13 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
348 dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label); 349 dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label);
349 dataKey.def = 'def' + this.columns.length; 350 dataKey.def = 'def' + this.columns.length;
350 const keySettings: TableWidgetDataKeySettings = dataKey.settings; 351 const keySettings: TableWidgetDataKeySettings = dataKey.settings;
  352 + if (dataKey.type === DataKeyType.entityField &&
  353 + !isDefined(keySettings.columnWidth) || keySettings.columnWidth === '0px') {
  354 + const entityField = entityFields[dataKey.name];
  355 + if (entityField && entityField.time) {
  356 + keySettings.columnWidth = '120px';
  357 + }
  358 + }
351 359
352 this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings); 360 this.stylesInfo[dataKey.def] = getCellStyleInfo(keySettings);
353 this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, entity, ctx'); 361 this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, entity, ctx');
@@ -595,7 +603,7 @@ class EntityDatasource implements DataSource<EntityData> { @@ -595,7 +603,7 @@ class EntityDatasource implements DataSource<EntityData> {
595 603
596 loadEntities(pageLink: EntityDataPageLink, sortOrderLabel: string, keyFilters: KeyFilter[]) { 604 loadEntities(pageLink: EntityDataPageLink, sortOrderLabel: string, keyFilters: KeyFilter[]) {
597 this.dataLoading = true; 605 this.dataLoading = true;
598 - this.clear(); 606 + // this.clear();
599 this.appliedPageLink = pageLink; 607 this.appliedPageLink = pageLink;
600 this.appliedSortOrderLabel = sortOrderLabel; 608 this.appliedSortOrderLabel = sortOrderLabel;
601 this.subscription.subscribeForPaginatedData(0, pageLink, keyFilters); 609 this.subscription.subscribeForPaginatedData(0, pageLink, keyFilters);