Showing
27 changed files
with
1472 additions
and
11 deletions
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.controller; | ||
17 | + | ||
18 | +import org.springframework.security.access.prepost.PreAuthorize; | ||
19 | +import org.springframework.web.bind.annotation.*; | ||
20 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
21 | +import org.thingsboard.server.common.data.id.EntityIdFactory; | ||
22 | +import org.thingsboard.server.common.data.id.TenantId; | ||
23 | +import org.thingsboard.server.common.data.page.TimePageData; | ||
24 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
25 | +import org.thingsboard.server.exception.ThingsboardException; | ||
26 | + | ||
27 | +@RestController | ||
28 | +@RequestMapping("/api") | ||
29 | +public class AuditLogController extends BaseController { | ||
30 | + | ||
31 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | ||
32 | + @RequestMapping(value = "/audit/logs/{entityType}/{entityId}", params = {"limit"}, method = RequestMethod.GET) | ||
33 | + @ResponseBody | ||
34 | + public TimePageData<AuditLog> getAuditLogs( | ||
35 | + @PathVariable("entityType") String strEntityType, | ||
36 | + @PathVariable("entityId") String strEntityId, | ||
37 | + @RequestParam int limit, | ||
38 | + @RequestParam(required = false) Long startTime, | ||
39 | + @RequestParam(required = false) Long endTime, | ||
40 | + @RequestParam(required = false, defaultValue = "false") boolean ascOrder, | ||
41 | + @RequestParam(required = false) String offset) throws ThingsboardException { | ||
42 | + try { | ||
43 | + checkParameter("EntityId", strEntityId); | ||
44 | + checkParameter("EntityType", strEntityType); | ||
45 | + TenantId tenantId = getCurrentUser().getTenantId(); | ||
46 | + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); | ||
47 | + return checkNotNull(auditLogService.findAuditLogsByTenantIdAndEntityId(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), pageLink)); | ||
48 | + } catch (Exception e) { | ||
49 | + throw handleException(e); | ||
50 | + } | ||
51 | + } | ||
52 | + | ||
53 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | ||
54 | + @RequestMapping(value = "/audit/logs", params = {"limit"}, method = RequestMethod.GET) | ||
55 | + @ResponseBody | ||
56 | + public TimePageData<AuditLog> getAuditLogs( | ||
57 | + @RequestParam int limit, | ||
58 | + @RequestParam(required = false) Long startTime, | ||
59 | + @RequestParam(required = false) Long endTime, | ||
60 | + @RequestParam(required = false, defaultValue = "false") boolean ascOrder, | ||
61 | + @RequestParam(required = false) String offset) throws ThingsboardException { | ||
62 | + try { | ||
63 | + TenantId tenantId = getCurrentUser().getTenantId(); | ||
64 | + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); | ||
65 | + return checkNotNull(auditLogService.findAuditLogsByTenantId(tenantId, pageLink)); | ||
66 | + } catch (Exception e) { | ||
67 | + throw handleException(e); | ||
68 | + } | ||
69 | + } | ||
70 | +} |
@@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.widget.WidgetType; | @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.widget.WidgetType; | ||
39 | import org.thingsboard.server.common.data.widget.WidgetsBundle; | 39 | import org.thingsboard.server.common.data.widget.WidgetsBundle; |
40 | import org.thingsboard.server.dao.alarm.AlarmService; | 40 | import org.thingsboard.server.dao.alarm.AlarmService; |
41 | import org.thingsboard.server.dao.asset.AssetService; | 41 | import org.thingsboard.server.dao.asset.AssetService; |
42 | +import org.thingsboard.server.dao.audit.AuditLogService; | ||
42 | import org.thingsboard.server.dao.customer.CustomerService; | 43 | import org.thingsboard.server.dao.customer.CustomerService; |
43 | import org.thingsboard.server.dao.dashboard.DashboardService; | 44 | import org.thingsboard.server.dao.dashboard.DashboardService; |
44 | import org.thingsboard.server.dao.device.DeviceCredentialsService; | 45 | import org.thingsboard.server.dao.device.DeviceCredentialsService; |
@@ -117,6 +118,9 @@ public abstract class BaseController { | @@ -117,6 +118,9 @@ public abstract class BaseController { | ||
117 | @Autowired | 118 | @Autowired |
118 | protected RelationService relationService; | 119 | protected RelationService relationService; |
119 | 120 | ||
121 | + @Autowired | ||
122 | + protected AuditLogService auditLogService; | ||
123 | + | ||
120 | 124 | ||
121 | @ExceptionHandler(ThingsboardException.class) | 125 | @ExceptionHandler(ThingsboardException.class) |
122 | public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) { | 126 | public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) { |
@@ -22,6 +22,9 @@ import org.springframework.web.bind.annotation.*; | @@ -22,6 +22,9 @@ import org.springframework.web.bind.annotation.*; | ||
22 | import org.thingsboard.server.common.data.Customer; | 22 | import org.thingsboard.server.common.data.Customer; |
23 | import org.thingsboard.server.common.data.Device; | 23 | import org.thingsboard.server.common.data.Device; |
24 | import org.thingsboard.server.common.data.EntitySubtype; | 24 | import org.thingsboard.server.common.data.EntitySubtype; |
25 | +import org.thingsboard.server.common.data.audit.ActionStatus; | ||
26 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
27 | +import org.thingsboard.server.common.data.device.DeviceSearchQuery; | ||
25 | import org.thingsboard.server.common.data.id.CustomerId; | 28 | import org.thingsboard.server.common.data.id.CustomerId; |
26 | import org.thingsboard.server.common.data.id.DeviceId; | 29 | import org.thingsboard.server.common.data.id.DeviceId; |
27 | import org.thingsboard.server.common.data.id.TenantId; | 30 | import org.thingsboard.server.common.data.id.TenantId; |
@@ -29,7 +32,6 @@ import org.thingsboard.server.common.data.page.TextPageData; | @@ -29,7 +32,6 @@ import org.thingsboard.server.common.data.page.TextPageData; | ||
29 | import org.thingsboard.server.common.data.page.TextPageLink; | 32 | import org.thingsboard.server.common.data.page.TextPageLink; |
30 | import org.thingsboard.server.common.data.security.Authority; | 33 | import org.thingsboard.server.common.data.security.Authority; |
31 | import org.thingsboard.server.common.data.security.DeviceCredentials; | 34 | import org.thingsboard.server.common.data.security.DeviceCredentials; |
32 | -import org.thingsboard.server.common.data.device.DeviceSearchQuery; | ||
33 | import org.thingsboard.server.dao.exception.IncorrectParameterException; | 35 | import org.thingsboard.server.dao.exception.IncorrectParameterException; |
34 | import org.thingsboard.server.dao.model.ModelConstants; | 36 | import org.thingsboard.server.dao.model.ModelConstants; |
35 | import org.thingsboard.server.exception.ThingsboardErrorCode; | 37 | import org.thingsboard.server.exception.ThingsboardErrorCode; |
@@ -75,12 +77,21 @@ public class DeviceController extends BaseController { | @@ -75,12 +77,21 @@ public class DeviceController extends BaseController { | ||
75 | } | 77 | } |
76 | } | 78 | } |
77 | Device savedDevice = checkNotNull(deviceService.saveDevice(device)); | 79 | Device savedDevice = checkNotNull(deviceService.saveDevice(device)); |
80 | + | ||
78 | actorService | 81 | actorService |
79 | .onDeviceNameOrTypeUpdate( | 82 | .onDeviceNameOrTypeUpdate( |
80 | savedDevice.getTenantId(), | 83 | savedDevice.getTenantId(), |
81 | savedDevice.getId(), | 84 | savedDevice.getId(), |
82 | savedDevice.getName(), | 85 | savedDevice.getName(), |
83 | savedDevice.getType()); | 86 | savedDevice.getType()); |
87 | + | ||
88 | + // TODO: refactor to ANNOTATION usage | ||
89 | + if (device.getId() == null) { | ||
90 | + logDeviceAction(savedDevice, ActionType.ADDED); | ||
91 | + } else { | ||
92 | + logDeviceAction(savedDevice, ActionType.UPDATED); | ||
93 | + } | ||
94 | + | ||
84 | return savedDevice; | 95 | return savedDevice; |
85 | } catch (Exception e) { | 96 | } catch (Exception e) { |
86 | throw handleException(e); | 97 | throw handleException(e); |
@@ -94,8 +105,10 @@ public class DeviceController extends BaseController { | @@ -94,8 +105,10 @@ public class DeviceController extends BaseController { | ||
94 | checkParameter(DEVICE_ID, strDeviceId); | 105 | checkParameter(DEVICE_ID, strDeviceId); |
95 | try { | 106 | try { |
96 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); | 107 | DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); |
97 | - checkDeviceId(deviceId); | 108 | + Device device = checkDeviceId(deviceId); |
98 | deviceService.deleteDevice(deviceId); | 109 | deviceService.deleteDevice(deviceId); |
110 | + // TODO: refactor to ANNOTATION usage | ||
111 | + logDeviceAction(device, ActionType.DELETED); | ||
99 | } catch (Exception e) { | 112 | } catch (Exception e) { |
100 | throw handleException(e); | 113 | throw handleException(e); |
101 | } | 114 | } |
@@ -173,9 +186,11 @@ public class DeviceController extends BaseController { | @@ -173,9 +186,11 @@ public class DeviceController extends BaseController { | ||
173 | public DeviceCredentials saveDeviceCredentials(@RequestBody DeviceCredentials deviceCredentials) throws ThingsboardException { | 186 | public DeviceCredentials saveDeviceCredentials(@RequestBody DeviceCredentials deviceCredentials) throws ThingsboardException { |
174 | checkNotNull(deviceCredentials); | 187 | checkNotNull(deviceCredentials); |
175 | try { | 188 | try { |
176 | - checkDeviceId(deviceCredentials.getDeviceId()); | 189 | + Device device = checkDeviceId(deviceCredentials.getDeviceId()); |
177 | DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(deviceCredentials)); | 190 | DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(deviceCredentials)); |
178 | actorService.onCredentialsUpdate(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId()); | 191 | actorService.onCredentialsUpdate(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId()); |
192 | + // TODO: refactor to ANNOTATION usage | ||
193 | + logDeviceAction(device, ActionType.CREDENTIALS_UPDATED); | ||
179 | return result; | 194 | return result; |
180 | } catch (Exception e) { | 195 | } catch (Exception e) { |
181 | throw handleException(e); | 196 | throw handleException(e); |
@@ -307,4 +322,18 @@ public class DeviceController extends BaseController { | @@ -307,4 +322,18 @@ public class DeviceController extends BaseController { | ||
307 | } | 322 | } |
308 | } | 323 | } |
309 | 324 | ||
325 | + // TODO: refactor to ANNOTATION usage | ||
326 | + private void logDeviceAction(Device device, ActionType actionType) throws ThingsboardException { | ||
327 | + auditLogService.logAction( | ||
328 | + getCurrentUser().getTenantId(), | ||
329 | + device.getId(), | ||
330 | + device.getName(), | ||
331 | + device.getCustomerId(), | ||
332 | + getCurrentUser().getId(), | ||
333 | + getCurrentUser().getName(), | ||
334 | + actionType, | ||
335 | + null, | ||
336 | + ActionStatus.SUCCESS, | ||
337 | + null); | ||
338 | + } | ||
310 | } | 339 | } |
@@ -66,6 +66,7 @@ import org.thingsboard.server.common.data.User; | @@ -66,6 +66,7 @@ import org.thingsboard.server.common.data.User; | ||
66 | import org.thingsboard.server.common.data.id.TenantId; | 66 | import org.thingsboard.server.common.data.id.TenantId; |
67 | import org.thingsboard.server.common.data.id.UUIDBased; | 67 | import org.thingsboard.server.common.data.id.UUIDBased; |
68 | import org.thingsboard.server.common.data.page.TextPageLink; | 68 | import org.thingsboard.server.common.data.page.TextPageLink; |
69 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
69 | import org.thingsboard.server.common.data.security.Authority; | 70 | import org.thingsboard.server.common.data.security.Authority; |
70 | import org.thingsboard.server.config.ThingsboardSecurityConfiguration; | 71 | import org.thingsboard.server.config.ThingsboardSecurityConfiguration; |
71 | import org.thingsboard.server.service.mail.TestMailService; | 72 | import org.thingsboard.server.service.mail.TestMailService; |
@@ -331,6 +332,35 @@ public abstract class AbstractControllerTest { | @@ -331,6 +332,35 @@ public abstract class AbstractControllerTest { | ||
331 | return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType); | 332 | return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType); |
332 | } | 333 | } |
333 | 334 | ||
335 | + protected <T> T doGetTypedWithTimePageLink(String urlTemplate, TypeReference<T> responseType, | ||
336 | + TimePageLink pageLink, | ||
337 | + Object... urlVariables) throws Exception { | ||
338 | + List<Object> pageLinkVariables = new ArrayList<>(); | ||
339 | + urlTemplate += "limit={limit}"; | ||
340 | + pageLinkVariables.add(pageLink.getLimit()); | ||
341 | + if (pageLink.getStartTime() != null) { | ||
342 | + urlTemplate += "&startTime={startTime}"; | ||
343 | + pageLinkVariables.add(pageLink.getStartTime()); | ||
344 | + } | ||
345 | + if (pageLink.getEndTime() != null) { | ||
346 | + urlTemplate += "&endTime={endTime}"; | ||
347 | + pageLinkVariables.add(pageLink.getEndTime()); | ||
348 | + } | ||
349 | + if (pageLink.getIdOffset() != null) { | ||
350 | + urlTemplate += "&offset={offset}"; | ||
351 | + pageLinkVariables.add(pageLink.getIdOffset().toString()); | ||
352 | + } | ||
353 | + if (pageLink.isAscOrder()) { | ||
354 | + urlTemplate += "&ascOrder={ascOrder}"; | ||
355 | + pageLinkVariables.add(pageLink.isAscOrder()); | ||
356 | + } | ||
357 | + Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()]; | ||
358 | + System.arraycopy(urlVariables, 0, vars, 0, urlVariables.length); | ||
359 | + System.arraycopy(pageLinkVariables.toArray(), 0, vars, urlVariables.length, pageLinkVariables.size()); | ||
360 | + | ||
361 | + return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType); | ||
362 | + } | ||
363 | + | ||
334 | protected <T> T doPost(String urlTemplate, Class<T> responseClass, String... params) throws Exception { | 364 | protected <T> T doPost(String urlTemplate, Class<T> responseClass, String... params) throws Exception { |
335 | return readResponse(doPost(urlTemplate, params).andExpect(status().isOk()), responseClass); | 365 | return readResponse(doPost(urlTemplate, params).andExpect(status().isOk()), responseClass); |
336 | } | 366 | } |
application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.controller; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.core.type.TypeReference; | ||
19 | +import org.junit.After; | ||
20 | +import org.junit.Assert; | ||
21 | +import org.junit.Before; | ||
22 | +import org.junit.Test; | ||
23 | +import org.thingsboard.server.common.data.Device; | ||
24 | +import org.thingsboard.server.common.data.Tenant; | ||
25 | +import org.thingsboard.server.common.data.User; | ||
26 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
27 | +import org.thingsboard.server.common.data.page.TimePageData; | ||
28 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
29 | +import org.thingsboard.server.common.data.security.Authority; | ||
30 | + | ||
31 | +import java.util.ArrayList; | ||
32 | +import java.util.List; | ||
33 | + | ||
34 | +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||
35 | + | ||
36 | +public abstract class BaseAuditLogControllerTest extends AbstractControllerTest { | ||
37 | + | ||
38 | + private Tenant savedTenant; | ||
39 | + | ||
40 | + @Before | ||
41 | + public void beforeTest() throws Exception { | ||
42 | + loginSysAdmin(); | ||
43 | + | ||
44 | + Tenant tenant = new Tenant(); | ||
45 | + tenant.setTitle("My tenant"); | ||
46 | + savedTenant = doPost("/api/tenant", tenant, Tenant.class); | ||
47 | + Assert.assertNotNull(savedTenant); | ||
48 | + | ||
49 | + User tenantAdmin = new User(); | ||
50 | + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); | ||
51 | + tenantAdmin.setTenantId(savedTenant.getId()); | ||
52 | + tenantAdmin.setEmail("tenant2@thingsboard.org"); | ||
53 | + tenantAdmin.setFirstName("Joe"); | ||
54 | + tenantAdmin.setLastName("Downs"); | ||
55 | + | ||
56 | + createUserAndLogin(tenantAdmin, "testPassword1"); | ||
57 | + } | ||
58 | + | ||
59 | + @After | ||
60 | + public void afterTest() throws Exception { | ||
61 | + loginSysAdmin(); | ||
62 | + | ||
63 | + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) | ||
64 | + .andExpect(status().isOk()); | ||
65 | + } | ||
66 | + | ||
67 | + @Test | ||
68 | + public void testSaveDeviceAuditLogs() throws Exception { | ||
69 | + for (int i = 0; i < 178; i++) { | ||
70 | + Device device = new Device(); | ||
71 | + device.setName("Device" + i); | ||
72 | + device.setType("default"); | ||
73 | + doPost("/api/device", device, Device.class); | ||
74 | + } | ||
75 | + | ||
76 | + List<AuditLog> loadedAuditLogs = new ArrayList<>(); | ||
77 | + TimePageLink pageLink = new TimePageLink(23); | ||
78 | + TimePageData<AuditLog> pageData; | ||
79 | + do { | ||
80 | + pageData = doGetTypedWithTimePageLink("/api/audit/logs?", | ||
81 | + new TypeReference<TimePageData<AuditLog>>() { | ||
82 | + }, pageLink); | ||
83 | + loadedAuditLogs.addAll(pageData.getData()); | ||
84 | + if (pageData.hasNext()) { | ||
85 | + pageLink = pageData.getNextPageLink(); | ||
86 | + } | ||
87 | + } while (pageData.hasNext()); | ||
88 | + | ||
89 | + Assert.assertEquals(178, loadedAuditLogs.size()); | ||
90 | + } | ||
91 | + | ||
92 | + @Test | ||
93 | + public void testUpdateDeviceAuditLogs() throws Exception { | ||
94 | + Device device = new Device(); | ||
95 | + device.setName("Device name"); | ||
96 | + device.setType("default"); | ||
97 | + Device savedDevice = doPost("/api/device", device, Device.class); | ||
98 | + for (int i = 0; i < 178; i++) { | ||
99 | + savedDevice.setName("Device name" + i); | ||
100 | + doPost("/api/device", savedDevice, Device.class); | ||
101 | + } | ||
102 | + | ||
103 | + List<AuditLog> loadedAuditLogs = new ArrayList<>(); | ||
104 | + TimePageLink pageLink = new TimePageLink(23); | ||
105 | + TimePageData<AuditLog> pageData; | ||
106 | + do { | ||
107 | + pageData = doGetTypedWithTimePageLink("/api/audit/logs/DEVICE/" + savedDevice.getId().getId() + "?", | ||
108 | + new TypeReference<TimePageData<AuditLog>>() { | ||
109 | + }, pageLink); | ||
110 | + loadedAuditLogs.addAll(pageData.getData()); | ||
111 | + if (pageData.hasNext()) { | ||
112 | + pageLink = pageData.getNextPageLink(); | ||
113 | + } | ||
114 | + } while (pageData.hasNext()); | ||
115 | + | ||
116 | + Assert.assertEquals(179, loadedAuditLogs.size()); | ||
117 | + } | ||
118 | +} |
application/src/test/java/org/thingsboard/server/controller/nosql/AuditLogControllerNoSqlTest.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.controller.nosql; | ||
17 | + | ||
18 | +import org.thingsboard.server.controller.BaseAuditLogControllerTest; | ||
19 | +import org.thingsboard.server.dao.service.DaoNoSqlTest; | ||
20 | + | ||
21 | +@DaoNoSqlTest | ||
22 | +public class AuditLogControllerNoSqlTest extends BaseAuditLogControllerTest { | ||
23 | +} |
application/src/test/java/org/thingsboard/server/controller/sql/AuditLogControllerSqlTest.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.controller.sql; | ||
17 | + | ||
18 | +import org.thingsboard.server.controller.BaseAuditLogControllerTest; | ||
19 | +import org.thingsboard.server.dao.service.DaoSqlTest; | ||
20 | + | ||
21 | +@DaoSqlTest | ||
22 | +public class AuditLogControllerSqlTest extends BaseAuditLogControllerTest { | ||
23 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.common.data.audit; | ||
17 | + | ||
18 | +public enum ActionStatus { | ||
19 | + SUCCESS, FAILURE | ||
20 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.common.data.audit; | ||
17 | + | ||
18 | +public enum ActionType { | ||
19 | + ADDED, DELETED, UPDATED, ATTRIBUTE_UPDATED, ATTRIBUTE_DELETED, ATTRIBUTE_ADDED, RPC_CALL, CREDENTIALS_UPDATED | ||
20 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.common.data.audit; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.JsonNode; | ||
19 | +import lombok.Data; | ||
20 | +import lombok.EqualsAndHashCode; | ||
21 | +import org.thingsboard.server.common.data.BaseData; | ||
22 | +import org.thingsboard.server.common.data.id.*; | ||
23 | + | ||
24 | +@EqualsAndHashCode(callSuper = true) | ||
25 | +@Data | ||
26 | +public class AuditLog extends BaseData<AuditLogId> { | ||
27 | + | ||
28 | + private TenantId tenantId; | ||
29 | + private CustomerId customerId; | ||
30 | + private EntityId entityId; | ||
31 | + private String entityName; | ||
32 | + private UserId userId; | ||
33 | + private String userName; | ||
34 | + private ActionType actionType; | ||
35 | + private JsonNode actionData; | ||
36 | + private ActionStatus actionStatus; | ||
37 | + private String actionFailureDetails; | ||
38 | + | ||
39 | + public AuditLog() { | ||
40 | + super(); | ||
41 | + } | ||
42 | + | ||
43 | + public AuditLog(AuditLogId id) { | ||
44 | + super(id); | ||
45 | + } | ||
46 | + | ||
47 | + public AuditLog(AuditLog auditLog) { | ||
48 | + super(auditLog); | ||
49 | + this.tenantId = auditLog.getTenantId(); | ||
50 | + this.customerId = auditLog.getCustomerId(); | ||
51 | + this.entityId = auditLog.getEntityId(); | ||
52 | + this.entityName = auditLog.getEntityName(); | ||
53 | + this.userId = auditLog.getUserId(); | ||
54 | + this.userName = auditLog.getUserName(); | ||
55 | + this.actionType = auditLog.getActionType(); | ||
56 | + this.actionData = auditLog.getActionData(); | ||
57 | + this.actionStatus = auditLog.getActionStatus(); | ||
58 | + this.actionFailureDetails = auditLog.getActionFailureDetails(); | ||
59 | + } | ||
60 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.common.data.id; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.annotation.JsonCreator; | ||
19 | +import com.fasterxml.jackson.annotation.JsonProperty; | ||
20 | + | ||
21 | +import java.util.UUID; | ||
22 | + | ||
23 | +public class AuditLogId extends UUIDBased { | ||
24 | + | ||
25 | + private static final long serialVersionUID = 1L; | ||
26 | + | ||
27 | + @JsonCreator | ||
28 | + public AuditLogId(@JsonProperty("id") UUID id) { | ||
29 | + super(id); | ||
30 | + } | ||
31 | + | ||
32 | + public static AuditLogId fromString(String auditLogId) { | ||
33 | + return new AuditLogId(UUID.fromString(auditLogId)); | ||
34 | + } | ||
35 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.audit; | ||
17 | + | ||
18 | +import com.google.common.util.concurrent.ListenableFuture; | ||
19 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
20 | +import org.thingsboard.server.common.data.id.EntityId; | ||
21 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
22 | + | ||
23 | +import java.util.List; | ||
24 | +import java.util.UUID; | ||
25 | + | ||
26 | +public interface AuditLogDao { | ||
27 | + | ||
28 | + ListenableFuture<Void> saveByTenantId(AuditLog auditLog); | ||
29 | + | ||
30 | + ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog); | ||
31 | + | ||
32 | + ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog); | ||
33 | + | ||
34 | + List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink); | ||
35 | + | ||
36 | + List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink); | ||
37 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.audit; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.JsonNode; | ||
19 | +import com.google.common.util.concurrent.ListenableFuture; | ||
20 | +import org.thingsboard.server.common.data.audit.ActionStatus; | ||
21 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
22 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
23 | +import org.thingsboard.server.common.data.id.CustomerId; | ||
24 | +import org.thingsboard.server.common.data.id.EntityId; | ||
25 | +import org.thingsboard.server.common.data.id.TenantId; | ||
26 | +import org.thingsboard.server.common.data.id.UserId; | ||
27 | +import org.thingsboard.server.common.data.page.TimePageData; | ||
28 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
29 | + | ||
30 | +import java.util.List; | ||
31 | + | ||
32 | +public interface AuditLogService { | ||
33 | + | ||
34 | + TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink); | ||
35 | + | ||
36 | + TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink); | ||
37 | + | ||
38 | + ListenableFuture<List<Void>> logAction(TenantId tenantId, | ||
39 | + EntityId entityId, | ||
40 | + String entityName, | ||
41 | + CustomerId customerId, | ||
42 | + UserId userId, | ||
43 | + String userName, | ||
44 | + ActionType actionType, | ||
45 | + JsonNode actionData, | ||
46 | + ActionStatus actionStatus, | ||
47 | + String actionFailureDetails); | ||
48 | + | ||
49 | + | ||
50 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.audit; | ||
17 | + | ||
18 | +import com.fasterxml.jackson.databind.JsonNode; | ||
19 | +import com.google.common.collect.Lists; | ||
20 | +import com.google.common.util.concurrent.Futures; | ||
21 | +import com.google.common.util.concurrent.ListenableFuture; | ||
22 | +import lombok.extern.slf4j.Slf4j; | ||
23 | +import org.springframework.beans.factory.annotation.Autowired; | ||
24 | +import org.springframework.stereotype.Service; | ||
25 | +import org.thingsboard.server.common.data.audit.ActionStatus; | ||
26 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
27 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
28 | +import org.thingsboard.server.common.data.id.CustomerId; | ||
29 | +import org.thingsboard.server.common.data.id.EntityId; | ||
30 | +import org.thingsboard.server.common.data.id.TenantId; | ||
31 | +import org.thingsboard.server.common.data.id.UserId; | ||
32 | +import org.thingsboard.server.common.data.page.TimePageData; | ||
33 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
34 | +import org.thingsboard.server.dao.exception.DataValidationException; | ||
35 | +import org.thingsboard.server.dao.service.DataValidator; | ||
36 | + | ||
37 | +import java.util.List; | ||
38 | + | ||
39 | +import static org.thingsboard.server.dao.service.Validator.validateEntityId; | ||
40 | +import static org.thingsboard.server.dao.service.Validator.validateId; | ||
41 | + | ||
42 | +@Slf4j | ||
43 | +@Service | ||
44 | +public class AuditLogServiceImpl implements AuditLogService { | ||
45 | + | ||
46 | + private static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; | ||
47 | + private static final int INSERTS_PER_ENTRY = 3; | ||
48 | + | ||
49 | + @Autowired | ||
50 | + private AuditLogDao auditLogDao; | ||
51 | + | ||
52 | + @Override | ||
53 | + public TimePageData<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { | ||
54 | + log.trace("Executing findAuditLogsByTenantIdAndEntityId [{}], [{}], [{}]", tenantId, entityId, pageLink); | ||
55 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | ||
56 | + validateEntityId(entityId, INCORRECT_TENANT_ID + entityId); | ||
57 | + List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantIdAndEntityId(tenantId.getId(), entityId, pageLink); | ||
58 | + return new TimePageData<>(auditLogs, pageLink); | ||
59 | + } | ||
60 | + | ||
61 | + @Override | ||
62 | + public TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) { | ||
63 | + log.trace("Executing findAuditLogs [{}]", pageLink); | ||
64 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | ||
65 | + List<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantId(tenantId.getId(), pageLink); | ||
66 | + return new TimePageData<>(auditLogs, pageLink); | ||
67 | + } | ||
68 | + | ||
69 | + private AuditLog createAuditLogEntry(TenantId tenantId, | ||
70 | + EntityId entityId, | ||
71 | + String entityName, | ||
72 | + CustomerId customerId, | ||
73 | + UserId userId, | ||
74 | + String userName, | ||
75 | + ActionType actionType, | ||
76 | + JsonNode actionData, | ||
77 | + ActionStatus actionStatus, | ||
78 | + String actionFailureDetails) { | ||
79 | + AuditLog result = new AuditLog(); | ||
80 | + result.setTenantId(tenantId); | ||
81 | + result.setEntityId(entityId); | ||
82 | + result.setEntityName(entityName); | ||
83 | + result.setCustomerId(customerId); | ||
84 | + result.setUserId(userId); | ||
85 | + result.setUserName(userName); | ||
86 | + result.setActionType(actionType); | ||
87 | + result.setActionData(actionData); | ||
88 | + result.setActionStatus(actionStatus); | ||
89 | + result.setActionFailureDetails(actionFailureDetails); | ||
90 | + return result; | ||
91 | + } | ||
92 | + | ||
93 | + @Override | ||
94 | + public ListenableFuture<List<Void>> logAction(TenantId tenantId, | ||
95 | + EntityId entityId, | ||
96 | + String entityName, | ||
97 | + CustomerId customerId, | ||
98 | + UserId userId, | ||
99 | + String userName, | ||
100 | + ActionType actionType, | ||
101 | + JsonNode actionData, | ||
102 | + ActionStatus actionStatus, | ||
103 | + String actionFailureDetails) { | ||
104 | + AuditLog auditLogEntry = createAuditLogEntry(tenantId, entityId, entityName, customerId, userId, userName, | ||
105 | + actionType, actionData, actionStatus, actionFailureDetails); | ||
106 | + log.trace("Executing logAction [{}]", auditLogEntry); | ||
107 | + auditLogValidator.validate(auditLogEntry); | ||
108 | + List<ListenableFuture<Void>> futures = Lists.newArrayListWithExpectedSize(INSERTS_PER_ENTRY); | ||
109 | + futures.add(auditLogDao.savePartitionsByTenantId(auditLogEntry)); | ||
110 | + futures.add(auditLogDao.saveByTenantId(auditLogEntry)); | ||
111 | + futures.add(auditLogDao.saveByTenantIdAndEntityId(auditLogEntry)); | ||
112 | + return Futures.allAsList(futures); | ||
113 | + } | ||
114 | + | ||
115 | + private DataValidator<AuditLog> auditLogValidator = | ||
116 | + new DataValidator<AuditLog>() { | ||
117 | + @Override | ||
118 | + protected void validateDataImpl(AuditLog auditLog) { | ||
119 | + if (auditLog.getEntityId() == null) { | ||
120 | + throw new DataValidationException("Entity Id should be specified!"); | ||
121 | + } | ||
122 | + if (auditLog.getTenantId() == null) { | ||
123 | + throw new DataValidationException("Tenant Id should be specified!"); | ||
124 | + } | ||
125 | + if (auditLog.getUserId() == null) { | ||
126 | + throw new DataValidationException("User Id should be specified!"); | ||
127 | + } | ||
128 | + } | ||
129 | + }; | ||
130 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.audit; | ||
17 | + | ||
18 | +import com.datastax.driver.core.BoundStatement; | ||
19 | +import com.datastax.driver.core.PreparedStatement; | ||
20 | +import com.datastax.driver.core.ResultSet; | ||
21 | +import com.datastax.driver.core.ResultSetFuture; | ||
22 | +import com.datastax.driver.core.utils.UUIDs; | ||
23 | +import com.google.common.base.Function; | ||
24 | +import com.google.common.util.concurrent.Futures; | ||
25 | +import com.google.common.util.concurrent.ListenableFuture; | ||
26 | +import lombok.extern.slf4j.Slf4j; | ||
27 | +import org.springframework.beans.factory.annotation.Autowired; | ||
28 | +import org.springframework.beans.factory.annotation.Value; | ||
29 | +import org.springframework.core.env.Environment; | ||
30 | +import org.springframework.stereotype.Component; | ||
31 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
32 | +import org.thingsboard.server.common.data.id.AuditLogId; | ||
33 | +import org.thingsboard.server.common.data.id.EntityId; | ||
34 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
35 | +import org.thingsboard.server.dao.DaoUtil; | ||
36 | +import org.thingsboard.server.dao.model.ModelConstants; | ||
37 | +import org.thingsboard.server.dao.model.nosql.AuditLogEntity; | ||
38 | +import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTimeDao; | ||
39 | +import org.thingsboard.server.dao.timeseries.TsPartitionDate; | ||
40 | +import org.thingsboard.server.dao.util.NoSqlDao; | ||
41 | + | ||
42 | +import javax.annotation.Nullable; | ||
43 | +import javax.annotation.PostConstruct; | ||
44 | +import javax.annotation.PreDestroy; | ||
45 | +import java.time.Instant; | ||
46 | +import java.time.LocalDate; | ||
47 | +import java.time.LocalDateTime; | ||
48 | +import java.time.ZoneOffset; | ||
49 | +import java.util.Arrays; | ||
50 | +import java.util.List; | ||
51 | +import java.util.Optional; | ||
52 | +import java.util.UUID; | ||
53 | +import java.util.concurrent.ExecutorService; | ||
54 | +import java.util.concurrent.Executors; | ||
55 | + | ||
56 | +import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; | ||
57 | +import static org.thingsboard.server.dao.model.ModelConstants.*; | ||
58 | + | ||
59 | +@Component | ||
60 | +@Slf4j | ||
61 | +@NoSqlDao | ||
62 | +public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLogEntity, AuditLog> implements AuditLogDao { | ||
63 | + | ||
64 | + private static final String INSERT_INTO = "INSERT INTO "; | ||
65 | + | ||
66 | + @Autowired | ||
67 | + private Environment environment; | ||
68 | + | ||
69 | + @Override | ||
70 | + protected Class<AuditLogEntity> getColumnFamilyClass() { | ||
71 | + return AuditLogEntity.class; | ||
72 | + } | ||
73 | + | ||
74 | + @Override | ||
75 | + protected String getColumnFamilyName() { | ||
76 | + return AUDIT_LOG_COLUMN_FAMILY_NAME; | ||
77 | + } | ||
78 | + | ||
79 | + protected ExecutorService readResultsProcessingExecutor; | ||
80 | + | ||
81 | + @Value("${cassandra.query.ts_key_value_partitioning}") | ||
82 | + private String partitioning; | ||
83 | + private TsPartitionDate tsFormat; | ||
84 | + | ||
85 | + private PreparedStatement[] saveStmts; | ||
86 | + | ||
87 | + private boolean isInstall() { | ||
88 | + return environment.acceptsProfiles("install"); | ||
89 | + } | ||
90 | + | ||
91 | + @PostConstruct | ||
92 | + public void init() { | ||
93 | + if (!isInstall()) { | ||
94 | + Optional<TsPartitionDate> partition = TsPartitionDate.parse(partitioning); | ||
95 | + if (partition.isPresent()) { | ||
96 | + tsFormat = partition.get(); | ||
97 | + } else { | ||
98 | + log.warn("Incorrect configuration of partitioning {}", partitioning); | ||
99 | + throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!"); | ||
100 | + } | ||
101 | + } | ||
102 | + readResultsProcessingExecutor = Executors.newCachedThreadPool(); | ||
103 | + } | ||
104 | + | ||
105 | + @PreDestroy | ||
106 | + public void stopExecutor() { | ||
107 | + if (readResultsProcessingExecutor != null) { | ||
108 | + readResultsProcessingExecutor.shutdownNow(); | ||
109 | + } | ||
110 | + } | ||
111 | + | ||
112 | + private <T> ListenableFuture<T> getFuture(ResultSetFuture future, java.util.function.Function<ResultSet, T> transformer) { | ||
113 | + return Futures.transform(future, new Function<ResultSet, T>() { | ||
114 | + @Nullable | ||
115 | + @Override | ||
116 | + public T apply(@Nullable ResultSet input) { | ||
117 | + return transformer.apply(input); | ||
118 | + } | ||
119 | + }, readResultsProcessingExecutor); | ||
120 | + } | ||
121 | + | ||
122 | + @Override | ||
123 | + public ListenableFuture<Void> saveByTenantId(AuditLog auditLog) { | ||
124 | + log.debug("Save saveByTenantId [{}] ", auditLog); | ||
125 | + | ||
126 | + AuditLogId auditLogId = new AuditLogId(UUIDs.timeBased()); | ||
127 | + | ||
128 | + long partition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); | ||
129 | + BoundStatement stmt = getSaveByTenantStmt().bind(); | ||
130 | + stmt.setUUID(0, auditLogId.getId()) | ||
131 | + .setUUID(1, auditLog.getTenantId().getId()) | ||
132 | + .setUUID(2, auditLog.getEntityId().getId()) | ||
133 | + .setString(3, auditLog.getEntityId().getEntityType().name()) | ||
134 | + .setString(4, auditLog.getActionType().name()) | ||
135 | + .setLong(5, partition); | ||
136 | + return getFuture(executeAsyncWrite(stmt), rs -> null); | ||
137 | + } | ||
138 | + | ||
139 | + @Override | ||
140 | + public ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog) { | ||
141 | + log.debug("Save saveByTenantIdAndEntityId [{}] ", auditLog); | ||
142 | + | ||
143 | + AuditLogId auditLogId = new AuditLogId(UUIDs.timeBased()); | ||
144 | + | ||
145 | + BoundStatement stmt = getSaveByTenantIdAndEntityIdStmt().bind(); | ||
146 | + stmt.setUUID(0, auditLogId.getId()) | ||
147 | + .setUUID(1, auditLog.getTenantId().getId()) | ||
148 | + .setUUID(2, auditLog.getEntityId().getId()) | ||
149 | + .setString(3, auditLog.getEntityId().getEntityType().name()) | ||
150 | + .setString(4, auditLog.getActionType().name()); | ||
151 | + return getFuture(executeAsyncWrite(stmt), rs -> null); | ||
152 | + } | ||
153 | + | ||
154 | + @Override | ||
155 | + public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) { | ||
156 | + log.debug("Save savePartitionsByTenantId [{}] ", auditLog); | ||
157 | + | ||
158 | + long partition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); | ||
159 | + | ||
160 | + BoundStatement stmt = getPartitionInsertStmt().bind(); | ||
161 | + stmt = stmt.setUUID(0, auditLog.getTenantId().getId()) | ||
162 | + .setLong(1, partition); | ||
163 | + return getFuture(executeAsyncWrite(stmt), rs -> null); | ||
164 | + } | ||
165 | + | ||
166 | + private PreparedStatement getPartitionInsertStmt() { | ||
167 | + // TODO: ADD CACHE LOGIC | ||
168 | + return getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF + | ||
169 | + "(" + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY + | ||
170 | + "," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" + | ||
171 | + " VALUES(?, ?)"); | ||
172 | + } | ||
173 | + | ||
174 | + private PreparedStatement getSaveByTenantIdAndEntityIdStmt() { | ||
175 | + // TODO: ADD CACHE LOGIC | ||
176 | + return getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_ENTITY_ID_CF + | ||
177 | + "(" + ModelConstants.AUDIT_LOG_ID_PROPERTY + | ||
178 | + "," + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY + | ||
179 | + "," + ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY + | ||
180 | + "," + ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY + | ||
181 | + "," + ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY + ")" + | ||
182 | + " VALUES(?, ?, ?, ?, ?)"); | ||
183 | + } | ||
184 | + | ||
185 | + private PreparedStatement getSaveByTenantStmt() { | ||
186 | + // TODO: ADD CACHE LOGIC | ||
187 | + return getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_CF + | ||
188 | + "(" + ModelConstants.AUDIT_LOG_ID_PROPERTY + | ||
189 | + "," + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY + | ||
190 | + "," + ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY + | ||
191 | + "," + ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY + | ||
192 | + "," + ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY + | ||
193 | + "," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" + | ||
194 | + " VALUES(?, ?, ?, ?, ?, ?)"); | ||
195 | + } | ||
196 | + | ||
197 | + private long toPartitionTs(long ts) { | ||
198 | + LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); | ||
199 | + return tsFormat.truncatedTo(time).toInstant(ZoneOffset.UTC).toEpochMilli(); | ||
200 | + } | ||
201 | + | ||
202 | + | ||
203 | + @Override | ||
204 | + public List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) { | ||
205 | + log.trace("Try to find audit logs by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink); | ||
206 | + List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_ENTITY_ID_CF, | ||
207 | + Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId), | ||
208 | + eq(ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY, entityId.getEntityType()), | ||
209 | + eq(ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY, entityId.getId())), | ||
210 | + pageLink); | ||
211 | + log.trace("Found audit logs by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink); | ||
212 | + return DaoUtil.convertDataList(entities); | ||
213 | + } | ||
214 | + | ||
215 | + @Override | ||
216 | + public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) { | ||
217 | + log.trace("Try to find audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink); | ||
218 | + | ||
219 | + // TODO: ADD AUDIT LOG PARTITION CURSOR LOGIC | ||
220 | + | ||
221 | + long minPartition; | ||
222 | + long maxPartition; | ||
223 | + | ||
224 | + if (pageLink.getStartTime() != null && pageLink.getStartTime() != 0) { | ||
225 | + minPartition = toPartitionTs(pageLink.getStartTime()); | ||
226 | + } else { | ||
227 | + minPartition = toPartitionTs(LocalDate.now().minusMonths(1).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); | ||
228 | + } | ||
229 | + | ||
230 | + if (pageLink.getEndTime() != null && pageLink.getEndTime() != 0) { | ||
231 | + maxPartition = toPartitionTs(pageLink.getEndTime()); | ||
232 | + } else { | ||
233 | + maxPartition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); | ||
234 | + } | ||
235 | + List<AuditLogEntity> entities = findPageWithTimeSearch(AUDIT_LOG_BY_TENANT_ID_CF, | ||
236 | + Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId), | ||
237 | + eq(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY, maxPartition)), | ||
238 | + pageLink); | ||
239 | + log.trace("Found audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink); | ||
240 | + return DaoUtil.convertDataList(entities); | ||
241 | + } | ||
242 | +} |
@@ -44,6 +44,13 @@ public class ModelConstants { | @@ -44,6 +44,13 @@ public class ModelConstants { | ||
44 | public static final String ADDITIONAL_INFO_PROPERTY = "additional_info"; | 44 | public static final String ADDITIONAL_INFO_PROPERTY = "additional_info"; |
45 | public static final String ENTITY_TYPE_PROPERTY = "entity_type"; | 45 | public static final String ENTITY_TYPE_PROPERTY = "entity_type"; |
46 | 46 | ||
47 | + public static final String ENTITY_TYPE_COLUMN = ENTITY_TYPE_PROPERTY; | ||
48 | + public static final String ENTITY_ID_COLUMN = "entity_id"; | ||
49 | + public static final String ATTRIBUTE_TYPE_COLUMN = "attribute_type"; | ||
50 | + public static final String ATTRIBUTE_KEY_COLUMN = "attribute_key"; | ||
51 | + public static final String LAST_UPDATE_TS_COLUMN = "last_update_ts"; | ||
52 | + | ||
53 | + | ||
47 | /** | 54 | /** |
48 | * Cassandra user constants. | 55 | * Cassandra user constants. |
49 | */ | 56 | */ |
@@ -135,6 +142,29 @@ public class ModelConstants { | @@ -135,6 +142,29 @@ public class ModelConstants { | ||
135 | public static final String DEVICE_TYPES_BY_TENANT_VIEW_NAME = "device_types_by_tenant"; | 142 | public static final String DEVICE_TYPES_BY_TENANT_VIEW_NAME = "device_types_by_tenant"; |
136 | 143 | ||
137 | /** | 144 | /** |
145 | + * Cassandra audit log constants. | ||
146 | + */ | ||
147 | + public static final String AUDIT_LOG_COLUMN_FAMILY_NAME = "audit_log"; | ||
148 | + | ||
149 | + public static final String AUDIT_LOG_BY_ENTITY_ID_CF = "audit_log_by_entity_id"; | ||
150 | + public static final String AUDIT_LOG_BY_TENANT_ID_CF = "audit_log_by_tenant_id"; | ||
151 | + public static final String AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF = "audit_log_by_tenant_id_partitions"; | ||
152 | + | ||
153 | + public static final String AUDIT_LOG_ID_PROPERTY = ID_PROPERTY; | ||
154 | + public static final String AUDIT_LOG_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; | ||
155 | + public static final String AUDIT_LOG_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY; | ||
156 | + public static final String AUDIT_LOG_ENTITY_TYPE_PROPERTY = ENTITY_TYPE_PROPERTY; | ||
157 | + public static final String AUDIT_LOG_ENTITY_ID_PROPERTY = ENTITY_ID_COLUMN; | ||
158 | + public static final String AUDIT_LOG_ENTITY_NAME_PROPERTY = "entity_name"; | ||
159 | + public static final String AUDIT_LOG_USER_ID_PROPERTY = USER_ID_PROPERTY; | ||
160 | + public static final String AUDIT_LOG_PARTITION_PROPERTY = "partition"; | ||
161 | + public static final String AUDIT_LOG_USER_NAME_PROPERTY = "user_name"; | ||
162 | + public static final String AUDIT_LOG_ACTION_TYPE_PROPERTY = "action_type"; | ||
163 | + public static final String AUDIT_LOG_ACTION_DATA_PROPERTY = "action_data"; | ||
164 | + public static final String AUDIT_LOG_ACTION_STATUS_PROPERTY = "action_status"; | ||
165 | + public static final String AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY = "action_failure_details"; | ||
166 | + | ||
167 | + /** | ||
138 | * Cassandra asset constants. | 168 | * Cassandra asset constants. |
139 | */ | 169 | */ |
140 | public static final String ASSET_COLUMN_FAMILY_NAME = "asset"; | 170 | public static final String ASSET_COLUMN_FAMILY_NAME = "asset"; |
@@ -310,13 +340,6 @@ public class ModelConstants { | @@ -310,13 +340,6 @@ public class ModelConstants { | ||
310 | public static final String TS_KV_PARTITIONS_CF = "ts_kv_partitions_cf"; | 340 | public static final String TS_KV_PARTITIONS_CF = "ts_kv_partitions_cf"; |
311 | public static final String TS_KV_LATEST_CF = "ts_kv_latest_cf"; | 341 | public static final String TS_KV_LATEST_CF = "ts_kv_latest_cf"; |
312 | 342 | ||
313 | - | ||
314 | - public static final String ENTITY_TYPE_COLUMN = ENTITY_TYPE_PROPERTY; | ||
315 | - public static final String ENTITY_ID_COLUMN = "entity_id"; | ||
316 | - public static final String ATTRIBUTE_TYPE_COLUMN = "attribute_type"; | ||
317 | - public static final String ATTRIBUTE_KEY_COLUMN = "attribute_key"; | ||
318 | - public static final String LAST_UPDATE_TS_COLUMN = "last_update_ts"; | ||
319 | - | ||
320 | public static final String PARTITION_COLUMN = "partition"; | 343 | public static final String PARTITION_COLUMN = "partition"; |
321 | public static final String KEY_COLUMN = "key"; | 344 | public static final String KEY_COLUMN = "key"; |
322 | public static final String TS_COLUMN = "ts"; | 345 | public static final String TS_COLUMN = "ts"; |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.model.nosql; | ||
17 | + | ||
18 | +import com.datastax.driver.mapping.annotations.Column; | ||
19 | +import com.datastax.driver.mapping.annotations.Table; | ||
20 | +import com.fasterxml.jackson.databind.JsonNode; | ||
21 | +import lombok.Data; | ||
22 | +import lombok.NoArgsConstructor; | ||
23 | +import org.thingsboard.server.common.data.EntityType; | ||
24 | +import org.thingsboard.server.common.data.audit.ActionStatus; | ||
25 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
26 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
27 | +import org.thingsboard.server.common.data.id.*; | ||
28 | +import org.thingsboard.server.dao.model.BaseEntity; | ||
29 | +import org.thingsboard.server.dao.model.type.ActionStatusCodec; | ||
30 | +import org.thingsboard.server.dao.model.type.ActionTypeCodec; | ||
31 | +import org.thingsboard.server.dao.model.type.EntityTypeCodec; | ||
32 | +import org.thingsboard.server.dao.model.type.JsonCodec; | ||
33 | + | ||
34 | +import java.util.UUID; | ||
35 | + | ||
36 | +import static org.thingsboard.server.dao.model.ModelConstants.*; | ||
37 | + | ||
38 | +@Table(name = AUDIT_LOG_COLUMN_FAMILY_NAME) | ||
39 | +@Data | ||
40 | +@NoArgsConstructor | ||
41 | +public class AuditLogEntity implements BaseEntity<AuditLog> { | ||
42 | + | ||
43 | + @Column(name = ID_PROPERTY) | ||
44 | + private UUID id; | ||
45 | + | ||
46 | + @Column(name = AUDIT_LOG_TENANT_ID_PROPERTY) | ||
47 | + private UUID tenantId; | ||
48 | + | ||
49 | + @Column(name = AUDIT_LOG_CUSTOMER_ID_PROPERTY) | ||
50 | + private UUID customerId; | ||
51 | + | ||
52 | + @Column(name = AUDIT_LOG_ENTITY_TYPE_PROPERTY, codec = EntityTypeCodec.class) | ||
53 | + private EntityType entityType; | ||
54 | + | ||
55 | + @Column(name = AUDIT_LOG_ENTITY_ID_PROPERTY) | ||
56 | + private UUID entityId; | ||
57 | + | ||
58 | + @Column(name = AUDIT_LOG_ENTITY_NAME_PROPERTY) | ||
59 | + private String entityName; | ||
60 | + | ||
61 | + @Column(name = AUDIT_LOG_USER_ID_PROPERTY) | ||
62 | + private UUID userId; | ||
63 | + | ||
64 | + @Column(name = AUDIT_LOG_USER_NAME_PROPERTY) | ||
65 | + private String userName; | ||
66 | + | ||
67 | + @Column(name = AUDIT_LOG_ACTION_TYPE_PROPERTY, codec = ActionTypeCodec.class) | ||
68 | + private ActionType actionType; | ||
69 | + | ||
70 | + @Column(name = AUDIT_LOG_ACTION_DATA_PROPERTY, codec = JsonCodec.class) | ||
71 | + private JsonNode actionData; | ||
72 | + | ||
73 | + @Column(name = AUDIT_LOG_ACTION_STATUS_PROPERTY, codec = ActionStatusCodec.class) | ||
74 | + private ActionStatus actionStatus; | ||
75 | + | ||
76 | + @Column(name = AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY) | ||
77 | + private String actionFailureDetails; | ||
78 | + | ||
79 | + @Override | ||
80 | + public UUID getId() { | ||
81 | + return id; | ||
82 | + } | ||
83 | + | ||
84 | + @Override | ||
85 | + public void setId(UUID id) { | ||
86 | + this.id = id; | ||
87 | + } | ||
88 | + | ||
89 | + public AuditLogEntity(AuditLog auditLog) { | ||
90 | + if (auditLog.getId() != null) { | ||
91 | + this.id = auditLog.getId().getId(); | ||
92 | + } | ||
93 | + if (auditLog.getTenantId() != null) { | ||
94 | + this.tenantId = auditLog.getTenantId().getId(); | ||
95 | + } | ||
96 | + if (auditLog.getEntityId() != null) { | ||
97 | + this.entityType = auditLog.getEntityId().getEntityType(); | ||
98 | + this.entityId = auditLog.getEntityId().getId(); | ||
99 | + } | ||
100 | + if (auditLog.getCustomerId() != null) { | ||
101 | + this.customerId = auditLog.getCustomerId().getId(); | ||
102 | + } | ||
103 | + if (auditLog.getUserId() != null) { | ||
104 | + this.userId = auditLog.getUserId().getId(); | ||
105 | + } | ||
106 | + this.entityName = auditLog.getEntityName(); | ||
107 | + this.userName = auditLog.getUserName(); | ||
108 | + this.actionType = auditLog.getActionType(); | ||
109 | + this.actionData = auditLog.getActionData(); | ||
110 | + this.actionStatus = auditLog.getActionStatus(); | ||
111 | + this.actionFailureDetails = auditLog.getActionFailureDetails(); | ||
112 | + } | ||
113 | + | ||
114 | + @Override | ||
115 | + public AuditLog toData() { | ||
116 | + AuditLog auditLog = new AuditLog(new AuditLogId(id)); | ||
117 | + if (tenantId != null) { | ||
118 | + auditLog.setTenantId(new TenantId(tenantId)); | ||
119 | + } | ||
120 | + if (entityId != null & entityType != null) { | ||
121 | + auditLog.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId)); | ||
122 | + } | ||
123 | + if (customerId != null) { | ||
124 | + auditLog.setCustomerId(new CustomerId(customerId)); | ||
125 | + } | ||
126 | + if (userId != null) { | ||
127 | + auditLog.setUserId(new UserId(userId)); | ||
128 | + } | ||
129 | + auditLog.setEntityName(this.entityName); | ||
130 | + auditLog.setUserName(this.userName); | ||
131 | + auditLog.setActionType(this.actionType); | ||
132 | + auditLog.setActionData(this.actionData); | ||
133 | + auditLog.setActionStatus(this.actionStatus); | ||
134 | + auditLog.setActionFailureDetails(this.actionFailureDetails); | ||
135 | + return auditLog; | ||
136 | + } | ||
137 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.model.sql; | ||
17 | + | ||
18 | +import com.datastax.driver.core.utils.UUIDs; | ||
19 | +import com.fasterxml.jackson.databind.JsonNode; | ||
20 | +import lombok.Data; | ||
21 | +import lombok.EqualsAndHashCode; | ||
22 | +import org.hibernate.annotations.Type; | ||
23 | +import org.hibernate.annotations.TypeDef; | ||
24 | +import org.thingsboard.server.common.data.EntityType; | ||
25 | +import org.thingsboard.server.common.data.audit.ActionStatus; | ||
26 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
27 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
28 | +import org.thingsboard.server.common.data.id.AuditLogId; | ||
29 | +import org.thingsboard.server.common.data.id.CustomerId; | ||
30 | +import org.thingsboard.server.common.data.id.EntityIdFactory; | ||
31 | +import org.thingsboard.server.common.data.id.TenantId; | ||
32 | +import org.thingsboard.server.dao.model.BaseEntity; | ||
33 | +import org.thingsboard.server.dao.model.BaseSqlEntity; | ||
34 | +import org.thingsboard.server.dao.model.ModelConstants; | ||
35 | +import org.thingsboard.server.dao.util.mapping.JsonStringType; | ||
36 | + | ||
37 | +import javax.persistence.*; | ||
38 | + | ||
39 | +import static org.thingsboard.server.dao.model.ModelConstants.*; | ||
40 | + | ||
41 | +@Data | ||
42 | +@EqualsAndHashCode(callSuper = true) | ||
43 | +@Entity | ||
44 | +@TypeDef(name = "json", typeClass = JsonStringType.class) | ||
45 | +@Table(name = ModelConstants.AUDIT_LOG_COLUMN_FAMILY_NAME) | ||
46 | +public class AuditLogEntity extends BaseSqlEntity<AuditLog> implements BaseEntity<AuditLog> { | ||
47 | + | ||
48 | + @Column(name = AUDIT_LOG_TENANT_ID_PROPERTY) | ||
49 | + private String tenantId; | ||
50 | + | ||
51 | + @Column(name = AUDIT_LOG_CUSTOMER_ID_PROPERTY) | ||
52 | + private String customerId; | ||
53 | + | ||
54 | + @Enumerated(EnumType.STRING) | ||
55 | + @Column(name = AUDIT_LOG_ENTITY_TYPE_PROPERTY) | ||
56 | + private EntityType entityType; | ||
57 | + | ||
58 | + @Column(name = AUDIT_LOG_ENTITY_ID_PROPERTY) | ||
59 | + private String entityId; | ||
60 | + | ||
61 | + @Column(name = AUDIT_LOG_ENTITY_NAME_PROPERTY) | ||
62 | + private String entityName; | ||
63 | + | ||
64 | + @Column(name = AUDIT_LOG_USER_ID_PROPERTY) | ||
65 | + private String userId; | ||
66 | + | ||
67 | + @Column(name = AUDIT_LOG_USER_NAME_PROPERTY) | ||
68 | + private String userName; | ||
69 | + | ||
70 | + @Enumerated(EnumType.STRING) | ||
71 | + @Column(name = AUDIT_LOG_ACTION_TYPE_PROPERTY) | ||
72 | + private ActionType actionType; | ||
73 | + | ||
74 | + @Type(type = "json") | ||
75 | + @Column(name = AUDIT_LOG_ACTION_DATA_PROPERTY) | ||
76 | + private JsonNode actionData; | ||
77 | + | ||
78 | + @Enumerated(EnumType.STRING) | ||
79 | + @Column(name = AUDIT_LOG_ACTION_STATUS_PROPERTY) | ||
80 | + private ActionStatus actionStatus; | ||
81 | + | ||
82 | + @Column(name = AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY) | ||
83 | + private String actionFailureDetails; | ||
84 | + | ||
85 | + public AuditLogEntity() { | ||
86 | + super(); | ||
87 | + } | ||
88 | + | ||
89 | + public AuditLogEntity(AuditLog auditLog) { | ||
90 | + if (auditLog.getId() != null) { | ||
91 | + this.setId(auditLog.getId().getId()); | ||
92 | + } | ||
93 | + if (auditLog.getTenantId() != null) { | ||
94 | + this.tenantId = toString(auditLog.getTenantId().getId()); | ||
95 | + } | ||
96 | + if (auditLog.getCustomerId() != null) { | ||
97 | + this.customerId = toString(auditLog.getCustomerId().getId()); | ||
98 | + } | ||
99 | + if (auditLog.getEntityId() != null) { | ||
100 | + this.entityId = toString(auditLog.getEntityId().getId()); | ||
101 | + this.entityType = auditLog.getEntityId().getEntityType(); | ||
102 | + } | ||
103 | + this.entityName = auditLog.getEntityName(); | ||
104 | + this.userName = auditLog.getUserName(); | ||
105 | + this.actionType = auditLog.getActionType(); | ||
106 | + this.actionData = auditLog.getActionData(); | ||
107 | + this.actionStatus = auditLog.getActionStatus(); | ||
108 | + this.actionFailureDetails = auditLog.getActionFailureDetails(); | ||
109 | + } | ||
110 | + | ||
111 | + @Override | ||
112 | + public AuditLog toData() { | ||
113 | + AuditLog auditLog = new AuditLog(new AuditLogId(getId())); | ||
114 | + auditLog.setCreatedTime(UUIDs.unixTimestamp(getId())); | ||
115 | + if (tenantId != null) { | ||
116 | + auditLog.setTenantId(new TenantId(toUUID(tenantId))); | ||
117 | + } | ||
118 | + if (customerId != null) { | ||
119 | + auditLog.setCustomerId(new CustomerId(toUUID(customerId))); | ||
120 | + } | ||
121 | + if (entityId != null) { | ||
122 | + auditLog.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), toUUID(entityId).toString())); | ||
123 | + } | ||
124 | + auditLog.setEntityName(this.entityName); | ||
125 | + auditLog.setUserName(this.userName); | ||
126 | + auditLog.setActionType(this.actionType); | ||
127 | + auditLog.setActionData(this.actionData); | ||
128 | + auditLog.setActionStatus(this.actionStatus); | ||
129 | + auditLog.setActionFailureDetails(this.actionFailureDetails); | ||
130 | + return auditLog; | ||
131 | + } | ||
132 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.model.type; | ||
17 | + | ||
18 | +import com.datastax.driver.extras.codecs.enums.EnumNameCodec; | ||
19 | +import org.thingsboard.server.common.data.audit.ActionStatus; | ||
20 | + | ||
21 | +public class ActionStatusCodec extends EnumNameCodec<ActionStatus> { | ||
22 | + | ||
23 | + public ActionStatusCodec() { | ||
24 | + super(ActionStatus.class); | ||
25 | + } | ||
26 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.model.type; | ||
17 | + | ||
18 | +import com.datastax.driver.extras.codecs.enums.EnumNameCodec; | ||
19 | +import org.thingsboard.server.common.data.audit.ActionType; | ||
20 | + | ||
21 | +public class ActionTypeCodec extends EnumNameCodec<ActionType> { | ||
22 | + | ||
23 | + public ActionTypeCodec() { | ||
24 | + super(ActionType.class); | ||
25 | + } | ||
26 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.sql.audit; | ||
17 | + | ||
18 | +import org.springframework.data.domain.Pageable; | ||
19 | +import org.springframework.data.jpa.repository.Query; | ||
20 | +import org.springframework.data.repository.CrudRepository; | ||
21 | +import org.springframework.data.repository.query.Param; | ||
22 | +import org.thingsboard.server.common.data.EntityType; | ||
23 | +import org.thingsboard.server.dao.model.sql.AuditLogEntity; | ||
24 | + | ||
25 | +import java.util.List; | ||
26 | + | ||
27 | +public interface AuditLogRepository extends CrudRepository<AuditLogEntity, String> { | ||
28 | + | ||
29 | + @Query("SELECT al FROM AuditLogEntity al WHERE al.tenantId = :tenantId " + | ||
30 | + "AND al.id > :idOffset ORDER BY al.id") | ||
31 | + List<AuditLogEntity> findByTenantId(@Param("tenantId") String tenantId, | ||
32 | + @Param("idOffset") String idOffset, | ||
33 | + Pageable pageable); | ||
34 | + | ||
35 | + @Query("SELECT al FROM AuditLogEntity al WHERE al.tenantId = :tenantId " + | ||
36 | + "AND al.entityType = :entityType " + | ||
37 | + "AND al.entityId = :entityId " + | ||
38 | + "AND al.id > :idOffset ORDER BY al.id") | ||
39 | + List<AuditLogEntity> findByTenantIdAndEntityId(@Param("tenantId") String tenantId, | ||
40 | + @Param("entityId") String entityId, | ||
41 | + @Param("entityType") EntityType entityType, | ||
42 | + @Param("idOffset") String idOffset, | ||
43 | + Pageable pageable); | ||
44 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.sql.audit; | ||
17 | + | ||
18 | +import com.google.common.util.concurrent.ListenableFuture; | ||
19 | +import com.google.common.util.concurrent.ListeningExecutorService; | ||
20 | +import com.google.common.util.concurrent.MoreExecutors; | ||
21 | +import org.springframework.beans.factory.annotation.Autowired; | ||
22 | +import org.springframework.data.domain.PageRequest; | ||
23 | +import org.springframework.data.repository.CrudRepository; | ||
24 | +import org.springframework.stereotype.Component; | ||
25 | +import org.thingsboard.server.common.data.audit.AuditLog; | ||
26 | +import org.thingsboard.server.common.data.id.EntityId; | ||
27 | +import org.thingsboard.server.common.data.page.TimePageLink; | ||
28 | +import org.thingsboard.server.dao.DaoUtil; | ||
29 | +import org.thingsboard.server.dao.audit.AuditLogDao; | ||
30 | +import org.thingsboard.server.dao.model.sql.AuditLogEntity; | ||
31 | +import org.thingsboard.server.dao.sql.JpaAbstractDao; | ||
32 | +import org.thingsboard.server.dao.util.SqlDao; | ||
33 | + | ||
34 | +import javax.annotation.PreDestroy; | ||
35 | +import java.util.List; | ||
36 | +import java.util.UUID; | ||
37 | +import java.util.concurrent.Executors; | ||
38 | + | ||
39 | +import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; | ||
40 | +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR; | ||
41 | + | ||
42 | +@Component | ||
43 | +@SqlDao | ||
44 | +public class JpaAuditLogDao extends JpaAbstractDao<AuditLogEntity, AuditLog> implements AuditLogDao { | ||
45 | + | ||
46 | + private ListeningExecutorService insertService = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); | ||
47 | + | ||
48 | + @Autowired | ||
49 | + private AuditLogRepository auditLogRepository; | ||
50 | + | ||
51 | + @Override | ||
52 | + protected Class<AuditLogEntity> getEntityClass() { | ||
53 | + return AuditLogEntity.class; | ||
54 | + } | ||
55 | + | ||
56 | + @Override | ||
57 | + protected CrudRepository<AuditLogEntity, String> getCrudRepository() { | ||
58 | + return auditLogRepository; | ||
59 | + } | ||
60 | + | ||
61 | + @PreDestroy | ||
62 | + void onDestroy() { | ||
63 | + insertService.shutdown(); | ||
64 | + } | ||
65 | + | ||
66 | + @Override | ||
67 | + public ListenableFuture<Void> saveByTenantId(AuditLog auditLog) { | ||
68 | + return insertService.submit(() -> { | ||
69 | + save(auditLog); | ||
70 | + return null; | ||
71 | + }); | ||
72 | + } | ||
73 | + | ||
74 | + @Override | ||
75 | + public ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog) { | ||
76 | + return insertService.submit(() -> null); | ||
77 | + } | ||
78 | + | ||
79 | + @Override | ||
80 | + public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) { | ||
81 | + return insertService.submit(() -> null); | ||
82 | + } | ||
83 | + | ||
84 | + @Override | ||
85 | + public List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) { | ||
86 | + return DaoUtil.convertDataList( | ||
87 | + auditLogRepository.findByTenantIdAndEntityId( | ||
88 | + fromTimeUUID(tenantId), | ||
89 | + fromTimeUUID(entityId.getId()), | ||
90 | + entityId.getEntityType(), | ||
91 | + pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), | ||
92 | + new PageRequest(0, pageLink.getLimit()))); | ||
93 | + } | ||
94 | + | ||
95 | + @Override | ||
96 | + public List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) { | ||
97 | + return DaoUtil.convertDataList( | ||
98 | + auditLogRepository.findByTenantId( | ||
99 | + fromTimeUUID(tenantId), | ||
100 | + pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), | ||
101 | + new PageRequest(0, pageLink.getLimit()))); | ||
102 | + } | ||
103 | +} |
@@ -548,3 +548,44 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.event_by_id AS | @@ -548,3 +548,44 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.event_by_id AS | ||
548 | AND event_type IS NOT NULL AND event_uid IS NOT NULL | 548 | AND event_type IS NOT NULL AND event_uid IS NOT NULL |
549 | PRIMARY KEY ((tenant_id, entity_type, entity_id), id, event_type, event_uid) | 549 | PRIMARY KEY ((tenant_id, entity_type, entity_id), id, event_type, event_uid) |
550 | WITH CLUSTERING ORDER BY (id ASC, event_type ASC, event_uid ASC); | 550 | WITH CLUSTERING ORDER BY (id ASC, event_type ASC, event_uid ASC); |
551 | + | ||
552 | + | ||
553 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_entity_id ( | ||
554 | + tenant_id timeuuid, | ||
555 | + id timeuuid, | ||
556 | + customer_id timeuuid, | ||
557 | + entity_id timeuuid, | ||
558 | + entity_type text, | ||
559 | + entity_name text, | ||
560 | + user_id timeuuid, | ||
561 | + user_name text, | ||
562 | + action_type text, | ||
563 | + action_data text, | ||
564 | + action_status text, | ||
565 | + action_failure_details text, | ||
566 | + PRIMARY KEY ((tenant_id, entity_id, entity_type), id) | ||
567 | +); | ||
568 | + | ||
569 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id ( | ||
570 | + tenant_id timeuuid, | ||
571 | + id timeuuid, | ||
572 | + partition bigint, | ||
573 | + customer_id timeuuid, | ||
574 | + entity_id timeuuid, | ||
575 | + entity_type text, | ||
576 | + entity_name text, | ||
577 | + user_id timeuuid, | ||
578 | + user_name text, | ||
579 | + action_type text, | ||
580 | + action_data text, | ||
581 | + action_status text, | ||
582 | + action_failure_details text, | ||
583 | + PRIMARY KEY ((tenant_id, partition), id) | ||
584 | +); | ||
585 | + | ||
586 | +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id_partitions ( | ||
587 | + tenant_id timeuuid, | ||
588 | + partition bigint, | ||
589 | + PRIMARY KEY (( tenant_id ), partition) | ||
590 | +) WITH CLUSTERING ORDER BY ( partition ASC ) | ||
591 | +AND compaction = { 'class' : 'LeveledCompactionStrategy' }; |
@@ -47,6 +47,22 @@ CREATE TABLE IF NOT EXISTS asset ( | @@ -47,6 +47,22 @@ CREATE TABLE IF NOT EXISTS asset ( | ||
47 | type varchar(255) | 47 | type varchar(255) |
48 | ); | 48 | ); |
49 | 49 | ||
50 | +CREATE TABLE IF NOT EXISTS audit_log ( | ||
51 | + id varchar(31) NOT NULL CONSTRAINT audit_log_pkey PRIMARY KEY, | ||
52 | + tenant_id varchar(31), | ||
53 | + customer_id varchar(31), | ||
54 | + entity_id varchar(31), | ||
55 | + entity_type varchar(255), | ||
56 | + entity_name varchar(255), | ||
57 | + user_id varchar(31), | ||
58 | + user_name varchar(255), | ||
59 | + action_type varchar(255), | ||
60 | + action_data varchar(255), | ||
61 | + action_status varchar(255), | ||
62 | + search_text varchar(255), | ||
63 | + action_failure_details varchar | ||
64 | +); | ||
65 | + | ||
50 | CREATE TABLE IF NOT EXISTS attribute_kv ( | 66 | CREATE TABLE IF NOT EXISTS attribute_kv ( |
51 | entity_type varchar(255), | 67 | entity_type varchar(255), |
52 | entity_id varchar(31), | 68 | entity_id varchar(31), |
@@ -24,7 +24,7 @@ import java.util.Arrays; | @@ -24,7 +24,7 @@ import java.util.Arrays; | ||
24 | 24 | ||
25 | @RunWith(ClasspathSuite.class) | 25 | @RunWith(ClasspathSuite.class) |
26 | @ClassnameFilters({ | 26 | @ClassnameFilters({ |
27 | - "org.thingsboard.server.dao.sql.*AAATest" | 27 | + "org.thingsboard.server.dao.sql.*THIS_MUST_BE_FIXED_Test" |
28 | }) | 28 | }) |
29 | public class JpaDaoTestSuite { | 29 | public class JpaDaoTestSuite { |
30 | 30 |
1 | +/** | ||
2 | + * Copyright © 2016-2017 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.dao.sql.audit; | ||
17 | + | ||
18 | +import org.thingsboard.server.dao.AbstractJpaDaoTest; | ||
19 | + | ||
20 | +public class JpaAuditLogDaoTest extends AbstractJpaDaoTest { | ||
21 | +} |
1 | DROP TABLE IF EXISTS admin_settings; | 1 | DROP TABLE IF EXISTS admin_settings; |
2 | DROP TABLE IF EXISTS alarm; | 2 | DROP TABLE IF EXISTS alarm; |
3 | DROP TABLE IF EXISTS asset; | 3 | DROP TABLE IF EXISTS asset; |
4 | +DROP TABLE IF EXISTS audit_log; | ||
4 | DROP TABLE IF EXISTS attribute_kv; | 5 | DROP TABLE IF EXISTS attribute_kv; |
5 | DROP TABLE IF EXISTS component_descriptor; | 6 | DROP TABLE IF EXISTS component_descriptor; |
6 | DROP TABLE IF EXISTS customer; | 7 | DROP TABLE IF EXISTS customer; |