From 3898e70cb261ca13ef3627b26db0d641d0daa51a Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Fri, 9 Feb 2018 17:17:44 +0200 Subject: [PATCH] Added audit log base impl --- .../server/controller/AuditLogController.java | 70 +++++ .../server/controller/BaseController.java | 4 + .../server/controller/DeviceController.java | 35 ++- .../controller/AbstractControllerTest.java | 30 +++ .../BaseAuditLogControllerTest.java | 118 +++++++++ .../nosql/AuditLogControllerNoSqlTest.java | 23 ++ .../sql/AuditLogControllerSqlTest.java | 23 ++ .../common/data/audit/ActionStatus.java | 20 ++ .../server/common/data/audit/ActionType.java | 20 ++ .../server/common/data/audit/AuditLog.java | 60 +++++ .../server/common/data/id/AuditLogId.java | 35 +++ .../server/dao/audit/AuditLogDao.java | 37 +++ .../server/dao/audit/AuditLogService.java | 50 ++++ .../server/dao/audit/AuditLogServiceImpl.java | 130 ++++++++++ .../dao/audit/CassandraAuditLogDao.java | 242 ++++++++++++++++++ .../server/dao/model/ModelConstants.java | 37 ++- .../dao/model/nosql/AuditLogEntity.java | 137 ++++++++++ .../server/dao/model/sql/AuditLogEntity.java | 132 ++++++++++ .../dao/model/type/ActionStatusCodec.java | 26 ++ .../dao/model/type/ActionTypeCodec.java | 26 ++ .../dao/sql/audit/AuditLogRepository.java | 44 ++++ .../server/dao/sql/audit/JpaAuditLogDao.java | 103 ++++++++ dao/src/main/resources/cassandra/schema.cql | 41 +++ dao/src/main/resources/sql/schema.sql | 16 ++ .../server/dao/JpaDaoTestSuite.java | 2 +- .../dao/sql/audit/JpaAuditLogDaoTest.java | 21 ++ .../test/resources/sql/drop-all-tables.sql | 1 + 27 files changed, 1472 insertions(+), 11 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/controller/AuditLogController.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/nosql/AuditLogControllerNoSqlTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/sql/AuditLogControllerSqlTest.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionStatus.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/audit/AuditLog.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/AuditLogId.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/nosql/AuditLogEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/AuditLogEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/type/ActionStatusCodec.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/type/ActionTypeCodec.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDaoTest.java diff --git a/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java b/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java new file mode 100644 index 0000000000..b79359b44f --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java @@ -0,0 +1,70 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.thingsboard.server.common.data.audit.AuditLog; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.exception.ThingsboardException; + +@RestController +@RequestMapping("/api") +public class AuditLogController extends BaseController { + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/audit/logs/{entityType}/{entityId}", params = {"limit"}, method = RequestMethod.GET) + @ResponseBody + public TimePageData getAuditLogs( + @PathVariable("entityType") String strEntityType, + @PathVariable("entityId") String strEntityId, + @RequestParam int limit, + @RequestParam(required = false) Long startTime, + @RequestParam(required = false) Long endTime, + @RequestParam(required = false, defaultValue = "false") boolean ascOrder, + @RequestParam(required = false) String offset) throws ThingsboardException { + try { + checkParameter("EntityId", strEntityId); + checkParameter("EntityType", strEntityType); + TenantId tenantId = getCurrentUser().getTenantId(); + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); + return checkNotNull(auditLogService.findAuditLogsByTenantIdAndEntityId(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), pageLink)); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/audit/logs", params = {"limit"}, method = RequestMethod.GET) + @ResponseBody + public TimePageData getAuditLogs( + @RequestParam int limit, + @RequestParam(required = false) Long startTime, + @RequestParam(required = false) Long endTime, + @RequestParam(required = false, defaultValue = "false") boolean ascOrder, + @RequestParam(required = false) String offset) throws ThingsboardException { + try { + TenantId tenantId = getCurrentUser().getTenantId(); + TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset); + return checkNotNull(auditLogService.findAuditLogsByTenantId(tenantId, pageLink)); + } catch (Exception e) { + throw handleException(e); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 16ba93eca8..ed5975a218 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.asset.AssetService; +import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceCredentialsService; @@ -117,6 +118,9 @@ public abstract class BaseController { @Autowired protected RelationService relationService; + @Autowired + protected AuditLogService auditLogService; + @ExceptionHandler(ThingsboardException.class) public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) { diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index eeb10c8c83..8d08706993 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -22,6 +22,9 @@ import org.springframework.web.bind.annotation.*; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntitySubtype; +import org.thingsboard.server.common.data.audit.ActionStatus; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; @@ -29,7 +32,6 @@ import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.exception.ThingsboardErrorCode; @@ -75,12 +77,21 @@ public class DeviceController extends BaseController { } } Device savedDevice = checkNotNull(deviceService.saveDevice(device)); + actorService .onDeviceNameOrTypeUpdate( savedDevice.getTenantId(), savedDevice.getId(), savedDevice.getName(), savedDevice.getType()); + + // TODO: refactor to ANNOTATION usage + if (device.getId() == null) { + logDeviceAction(savedDevice, ActionType.ADDED); + } else { + logDeviceAction(savedDevice, ActionType.UPDATED); + } + return savedDevice; } catch (Exception e) { throw handleException(e); @@ -94,8 +105,10 @@ public class DeviceController extends BaseController { checkParameter(DEVICE_ID, strDeviceId); try { DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - checkDeviceId(deviceId); + Device device = checkDeviceId(deviceId); deviceService.deleteDevice(deviceId); + // TODO: refactor to ANNOTATION usage + logDeviceAction(device, ActionType.DELETED); } catch (Exception e) { throw handleException(e); } @@ -173,9 +186,11 @@ public class DeviceController extends BaseController { public DeviceCredentials saveDeviceCredentials(@RequestBody DeviceCredentials deviceCredentials) throws ThingsboardException { checkNotNull(deviceCredentials); try { - checkDeviceId(deviceCredentials.getDeviceId()); + Device device = checkDeviceId(deviceCredentials.getDeviceId()); DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(deviceCredentials)); actorService.onCredentialsUpdate(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId()); + // TODO: refactor to ANNOTATION usage + logDeviceAction(device, ActionType.CREDENTIALS_UPDATED); return result; } catch (Exception e) { throw handleException(e); @@ -307,4 +322,18 @@ public class DeviceController extends BaseController { } } + // TODO: refactor to ANNOTATION usage + private void logDeviceAction(Device device, ActionType actionType) throws ThingsboardException { + auditLogService.logAction( + getCurrentUser().getTenantId(), + device.getId(), + device.getName(), + device.getCustomerId(), + getCurrentUser().getId(), + getCurrentUser().getName(), + actionType, + null, + ActionStatus.SUCCESS, + null); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java index 19e4329c33..36d736d6e7 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java @@ -66,6 +66,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.config.ThingsboardSecurityConfiguration; import org.thingsboard.server.service.mail.TestMailService; @@ -331,6 +332,35 @@ public abstract class AbstractControllerTest { return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType); } + protected T doGetTypedWithTimePageLink(String urlTemplate, TypeReference responseType, + TimePageLink pageLink, + Object... urlVariables) throws Exception { + List pageLinkVariables = new ArrayList<>(); + urlTemplate += "limit={limit}"; + pageLinkVariables.add(pageLink.getLimit()); + if (pageLink.getStartTime() != null) { + urlTemplate += "&startTime={startTime}"; + pageLinkVariables.add(pageLink.getStartTime()); + } + if (pageLink.getEndTime() != null) { + urlTemplate += "&endTime={endTime}"; + pageLinkVariables.add(pageLink.getEndTime()); + } + if (pageLink.getIdOffset() != null) { + urlTemplate += "&offset={offset}"; + pageLinkVariables.add(pageLink.getIdOffset().toString()); + } + if (pageLink.isAscOrder()) { + urlTemplate += "&ascOrder={ascOrder}"; + pageLinkVariables.add(pageLink.isAscOrder()); + } + Object[] vars = new Object[urlVariables.length + pageLinkVariables.size()]; + System.arraycopy(urlVariables, 0, vars, 0, urlVariables.length); + System.arraycopy(pageLinkVariables.toArray(), 0, vars, urlVariables.length, pageLinkVariables.size()); + + return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType); + } + protected T doPost(String urlTemplate, Class responseClass, String... params) throws Exception { return readResponse(doPost(urlTemplate, params).andExpect(status().isOk()), responseClass); } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java new file mode 100644 index 0000000000..5e74416868 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java @@ -0,0 +1,118 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.AuditLog; +import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.common.data.security.Authority; + +import java.util.ArrayList; +import java.util.List; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public abstract class BaseAuditLogControllerTest extends AbstractControllerTest { + + private Tenant savedTenant; + + @Before + public void beforeTest() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + User tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + createUserAndLogin(tenantAdmin, "testPassword1"); + } + + @After + public void afterTest() throws Exception { + loginSysAdmin(); + + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testSaveDeviceAuditLogs() throws Exception { + for (int i = 0; i < 178; i++) { + Device device = new Device(); + device.setName("Device" + i); + device.setType("default"); + doPost("/api/device", device, Device.class); + } + + List loadedAuditLogs = new ArrayList<>(); + TimePageLink pageLink = new TimePageLink(23); + TimePageData pageData; + do { + pageData = doGetTypedWithTimePageLink("/api/audit/logs?", + new TypeReference>() { + }, pageLink); + loadedAuditLogs.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Assert.assertEquals(178, loadedAuditLogs.size()); + } + + @Test + public void testUpdateDeviceAuditLogs() throws Exception { + Device device = new Device(); + device.setName("Device name"); + device.setType("default"); + Device savedDevice = doPost("/api/device", device, Device.class); + for (int i = 0; i < 178; i++) { + savedDevice.setName("Device name" + i); + doPost("/api/device", savedDevice, Device.class); + } + + List loadedAuditLogs = new ArrayList<>(); + TimePageLink pageLink = new TimePageLink(23); + TimePageData pageData; + do { + pageData = doGetTypedWithTimePageLink("/api/audit/logs/DEVICE/" + savedDevice.getId().getId() + "?", + new TypeReference>() { + }, pageLink); + loadedAuditLogs.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageData.getNextPageLink(); + } + } while (pageData.hasNext()); + + Assert.assertEquals(179, loadedAuditLogs.size()); + } +} diff --git a/application/src/test/java/org/thingsboard/server/controller/nosql/AuditLogControllerNoSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/nosql/AuditLogControllerNoSqlTest.java new file mode 100644 index 0000000000..4692afe303 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/nosql/AuditLogControllerNoSqlTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller.nosql; + +import org.thingsboard.server.controller.BaseAuditLogControllerTest; +import org.thingsboard.server.dao.service.DaoNoSqlTest; + +@DaoNoSqlTest +public class AuditLogControllerNoSqlTest extends BaseAuditLogControllerTest { +} diff --git a/application/src/test/java/org/thingsboard/server/controller/sql/AuditLogControllerSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/sql/AuditLogControllerSqlTest.java new file mode 100644 index 0000000000..df6804eea4 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/sql/AuditLogControllerSqlTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller.sql; + +import org.thingsboard.server.controller.BaseAuditLogControllerTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class AuditLogControllerSqlTest extends BaseAuditLogControllerTest { +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionStatus.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionStatus.java new file mode 100644 index 0000000000..5ee8beb398 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionStatus.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.audit; + +public enum ActionStatus { + SUCCESS, FAILURE +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java new file mode 100644 index 0000000000..495f80dc6f --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.audit; + +public enum ActionType { + ADDED, DELETED, UPDATED, ATTRIBUTE_UPDATED, ATTRIBUTE_DELETED, ATTRIBUTE_ADDED, RPC_CALL, CREDENTIALS_UPDATED +} \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/AuditLog.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/AuditLog.java new file mode 100644 index 0000000000..62d4bfac04 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/AuditLog.java @@ -0,0 +1,60 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.audit; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.id.*; + +@EqualsAndHashCode(callSuper = true) +@Data +public class AuditLog extends BaseData { + + private TenantId tenantId; + private CustomerId customerId; + private EntityId entityId; + private String entityName; + private UserId userId; + private String userName; + private ActionType actionType; + private JsonNode actionData; + private ActionStatus actionStatus; + private String actionFailureDetails; + + public AuditLog() { + super(); + } + + public AuditLog(AuditLogId id) { + super(id); + } + + public AuditLog(AuditLog auditLog) { + super(auditLog); + this.tenantId = auditLog.getTenantId(); + this.customerId = auditLog.getCustomerId(); + this.entityId = auditLog.getEntityId(); + this.entityName = auditLog.getEntityName(); + this.userId = auditLog.getUserId(); + this.userName = auditLog.getUserName(); + this.actionType = auditLog.getActionType(); + this.actionData = auditLog.getActionData(); + this.actionStatus = auditLog.getActionStatus(); + this.actionFailureDetails = auditLog.getActionFailureDetails(); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/AuditLogId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/AuditLogId.java new file mode 100644 index 0000000000..5327212739 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/AuditLogId.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.UUID; + +public class AuditLogId extends UUIDBased { + + private static final long serialVersionUID = 1L; + + @JsonCreator + public AuditLogId(@JsonProperty("id") UUID id) { + super(id); + } + + public static AuditLogId fromString(String auditLogId) { + return new AuditLogId(UUID.fromString(auditLogId)); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java new file mode 100644 index 0000000000..f401ad1ac6 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.audit; + +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.audit.AuditLog; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.page.TimePageLink; + +import java.util.List; +import java.util.UUID; + +public interface AuditLogDao { + + ListenableFuture saveByTenantId(AuditLog auditLog); + + ListenableFuture saveByTenantIdAndEntityId(AuditLog auditLog); + + ListenableFuture savePartitionsByTenantId(AuditLog auditLog); + + List findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink); + + List findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java new file mode 100644 index 0000000000..5593edee9d --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java @@ -0,0 +1,50 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.audit; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.audit.ActionStatus; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.audit.AuditLog; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.TimePageLink; + +import java.util.List; + +public interface AuditLogService { + + TimePageData findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink); + + TimePageData findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink); + + ListenableFuture> logAction(TenantId tenantId, + EntityId entityId, + String entityName, + CustomerId customerId, + UserId userId, + String userName, + ActionType actionType, + JsonNode actionData, + ActionStatus actionStatus, + String actionFailureDetails); + + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java new file mode 100644 index 0000000000..1baf573155 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java @@ -0,0 +1,130 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.audit; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.audit.ActionStatus; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.audit.AuditLog; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; + +import java.util.List; + +import static org.thingsboard.server.dao.service.Validator.validateEntityId; +import static org.thingsboard.server.dao.service.Validator.validateId; + +@Slf4j +@Service +public class AuditLogServiceImpl implements AuditLogService { + + private static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; + private static final int INSERTS_PER_ENTRY = 3; + + @Autowired + private AuditLogDao auditLogDao; + + @Override + public TimePageData findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink) { + log.trace("Executing findAuditLogsByTenantIdAndEntityId [{}], [{}], [{}]", tenantId, entityId, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateEntityId(entityId, INCORRECT_TENANT_ID + entityId); + List auditLogs = auditLogDao.findAuditLogsByTenantIdAndEntityId(tenantId.getId(), entityId, pageLink); + return new TimePageData<>(auditLogs, pageLink); + } + + @Override + public TimePageData findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) { + log.trace("Executing findAuditLogs [{}]", pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + List auditLogs = auditLogDao.findAuditLogsByTenantId(tenantId.getId(), pageLink); + return new TimePageData<>(auditLogs, pageLink); + } + + private AuditLog createAuditLogEntry(TenantId tenantId, + EntityId entityId, + String entityName, + CustomerId customerId, + UserId userId, + String userName, + ActionType actionType, + JsonNode actionData, + ActionStatus actionStatus, + String actionFailureDetails) { + AuditLog result = new AuditLog(); + result.setTenantId(tenantId); + result.setEntityId(entityId); + result.setEntityName(entityName); + result.setCustomerId(customerId); + result.setUserId(userId); + result.setUserName(userName); + result.setActionType(actionType); + result.setActionData(actionData); + result.setActionStatus(actionStatus); + result.setActionFailureDetails(actionFailureDetails); + return result; + } + + @Override + public ListenableFuture> logAction(TenantId tenantId, + EntityId entityId, + String entityName, + CustomerId customerId, + UserId userId, + String userName, + ActionType actionType, + JsonNode actionData, + ActionStatus actionStatus, + String actionFailureDetails) { + AuditLog auditLogEntry = createAuditLogEntry(tenantId, entityId, entityName, customerId, userId, userName, + actionType, actionData, actionStatus, actionFailureDetails); + log.trace("Executing logAction [{}]", auditLogEntry); + auditLogValidator.validate(auditLogEntry); + List> futures = Lists.newArrayListWithExpectedSize(INSERTS_PER_ENTRY); + futures.add(auditLogDao.savePartitionsByTenantId(auditLogEntry)); + futures.add(auditLogDao.saveByTenantId(auditLogEntry)); + futures.add(auditLogDao.saveByTenantIdAndEntityId(auditLogEntry)); + return Futures.allAsList(futures); + } + + private DataValidator auditLogValidator = + new DataValidator() { + @Override + protected void validateDataImpl(AuditLog auditLog) { + if (auditLog.getEntityId() == null) { + throw new DataValidationException("Entity Id should be specified!"); + } + if (auditLog.getTenantId() == null) { + throw new DataValidationException("Tenant Id should be specified!"); + } + if (auditLog.getUserId() == null) { + throw new DataValidationException("User Id should be specified!"); + } + } + }; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java new file mode 100644 index 0000000000..2fcb73281d --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java @@ -0,0 +1,242 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.audit; + +import com.datastax.driver.core.BoundStatement; +import com.datastax.driver.core.PreparedStatement; +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.ResultSetFuture; +import com.datastax.driver.core.utils.UUIDs; +import com.google.common.base.Function; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.audit.AuditLog; +import org.thingsboard.server.common.data.id.AuditLogId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.model.nosql.AuditLogEntity; +import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTimeDao; +import org.thingsboard.server.dao.timeseries.TsPartitionDate; +import org.thingsboard.server.dao.util.NoSqlDao; + +import javax.annotation.Nullable; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; +import static org.thingsboard.server.dao.model.ModelConstants.*; + +@Component +@Slf4j +@NoSqlDao +public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao implements AuditLogDao { + + private static final String INSERT_INTO = "INSERT INTO "; + + @Autowired + private Environment environment; + + @Override + protected Class getColumnFamilyClass() { + return AuditLogEntity.class; + } + + @Override + protected String getColumnFamilyName() { + return AUDIT_LOG_COLUMN_FAMILY_NAME; + } + + protected ExecutorService readResultsProcessingExecutor; + + @Value("${cassandra.query.ts_key_value_partitioning}") + private String partitioning; + private TsPartitionDate tsFormat; + + private PreparedStatement[] saveStmts; + + private boolean isInstall() { + return environment.acceptsProfiles("install"); + } + + @PostConstruct + public void init() { + if (!isInstall()) { + Optional partition = TsPartitionDate.parse(partitioning); + if (partition.isPresent()) { + tsFormat = partition.get(); + } else { + log.warn("Incorrect configuration of partitioning {}", partitioning); + throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!"); + } + } + readResultsProcessingExecutor = Executors.newCachedThreadPool(); + } + + @PreDestroy + public void stopExecutor() { + if (readResultsProcessingExecutor != null) { + readResultsProcessingExecutor.shutdownNow(); + } + } + + private ListenableFuture getFuture(ResultSetFuture future, java.util.function.Function transformer) { + return Futures.transform(future, new Function() { + @Nullable + @Override + public T apply(@Nullable ResultSet input) { + return transformer.apply(input); + } + }, readResultsProcessingExecutor); + } + + @Override + public ListenableFuture saveByTenantId(AuditLog auditLog) { + log.debug("Save saveByTenantId [{}] ", auditLog); + + AuditLogId auditLogId = new AuditLogId(UUIDs.timeBased()); + + long partition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); + BoundStatement stmt = getSaveByTenantStmt().bind(); + stmt.setUUID(0, auditLogId.getId()) + .setUUID(1, auditLog.getTenantId().getId()) + .setUUID(2, auditLog.getEntityId().getId()) + .setString(3, auditLog.getEntityId().getEntityType().name()) + .setString(4, auditLog.getActionType().name()) + .setLong(5, partition); + return getFuture(executeAsyncWrite(stmt), rs -> null); + } + + @Override + public ListenableFuture saveByTenantIdAndEntityId(AuditLog auditLog) { + log.debug("Save saveByTenantIdAndEntityId [{}] ", auditLog); + + AuditLogId auditLogId = new AuditLogId(UUIDs.timeBased()); + + BoundStatement stmt = getSaveByTenantIdAndEntityIdStmt().bind(); + stmt.setUUID(0, auditLogId.getId()) + .setUUID(1, auditLog.getTenantId().getId()) + .setUUID(2, auditLog.getEntityId().getId()) + .setString(3, auditLog.getEntityId().getEntityType().name()) + .setString(4, auditLog.getActionType().name()); + return getFuture(executeAsyncWrite(stmt), rs -> null); + } + + @Override + public ListenableFuture savePartitionsByTenantId(AuditLog auditLog) { + log.debug("Save savePartitionsByTenantId [{}] ", auditLog); + + long partition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); + + BoundStatement stmt = getPartitionInsertStmt().bind(); + stmt = stmt.setUUID(0, auditLog.getTenantId().getId()) + .setLong(1, partition); + return getFuture(executeAsyncWrite(stmt), rs -> null); + } + + private PreparedStatement getPartitionInsertStmt() { + // TODO: ADD CACHE LOGIC + return getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF + + "(" + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY + + "," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" + + " VALUES(?, ?)"); + } + + private PreparedStatement getSaveByTenantIdAndEntityIdStmt() { + // TODO: ADD CACHE LOGIC + return getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_ENTITY_ID_CF + + "(" + ModelConstants.AUDIT_LOG_ID_PROPERTY + + "," + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY + + "," + ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY + + "," + ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY + + "," + ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY + ")" + + " VALUES(?, ?, ?, ?, ?)"); + } + + private PreparedStatement getSaveByTenantStmt() { + // TODO: ADD CACHE LOGIC + return getSession().prepare(INSERT_INTO + ModelConstants.AUDIT_LOG_BY_TENANT_ID_CF + + "(" + ModelConstants.AUDIT_LOG_ID_PROPERTY + + "," + ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY + + "," + ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY + + "," + ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY + + "," + ModelConstants.AUDIT_LOG_ACTION_TYPE_PROPERTY + + "," + ModelConstants.AUDIT_LOG_PARTITION_PROPERTY + ")" + + " VALUES(?, ?, ?, ?, ?, ?)"); + } + + private long toPartitionTs(long ts) { + LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); + return tsFormat.truncatedTo(time).toInstant(ZoneOffset.UTC).toEpochMilli(); + } + + + @Override + public List findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) { + log.trace("Try to find audit logs by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink); + List entities = findPageWithTimeSearch(AUDIT_LOG_BY_ENTITY_ID_CF, + Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId), + eq(ModelConstants.AUDIT_LOG_ENTITY_TYPE_PROPERTY, entityId.getEntityType()), + eq(ModelConstants.AUDIT_LOG_ENTITY_ID_PROPERTY, entityId.getId())), + pageLink); + log.trace("Found audit logs by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink); + return DaoUtil.convertDataList(entities); + } + + @Override + public List findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) { + log.trace("Try to find audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink); + + // TODO: ADD AUDIT LOG PARTITION CURSOR LOGIC + + long minPartition; + long maxPartition; + + if (pageLink.getStartTime() != null && pageLink.getStartTime() != 0) { + minPartition = toPartitionTs(pageLink.getStartTime()); + } else { + minPartition = toPartitionTs(LocalDate.now().minusMonths(1).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); + } + + if (pageLink.getEndTime() != null && pageLink.getEndTime() != 0) { + maxPartition = toPartitionTs(pageLink.getEndTime()); + } else { + maxPartition = toPartitionTs(LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli()); + } + List entities = findPageWithTimeSearch(AUDIT_LOG_BY_TENANT_ID_CF, + Arrays.asList(eq(ModelConstants.AUDIT_LOG_TENANT_ID_PROPERTY, tenantId), + eq(ModelConstants.AUDIT_LOG_PARTITION_PROPERTY, maxPartition)), + pageLink); + log.trace("Found audit logs by tenant [{}] and pageLink [{}]", tenantId, pageLink); + return DaoUtil.convertDataList(entities); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 9b596fc4ca..58c8d2f1f7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -44,6 +44,13 @@ public class ModelConstants { public static final String ADDITIONAL_INFO_PROPERTY = "additional_info"; public static final String ENTITY_TYPE_PROPERTY = "entity_type"; + public static final String ENTITY_TYPE_COLUMN = ENTITY_TYPE_PROPERTY; + public static final String ENTITY_ID_COLUMN = "entity_id"; + public static final String ATTRIBUTE_TYPE_COLUMN = "attribute_type"; + public static final String ATTRIBUTE_KEY_COLUMN = "attribute_key"; + public static final String LAST_UPDATE_TS_COLUMN = "last_update_ts"; + + /** * Cassandra user constants. */ @@ -134,6 +141,29 @@ public class ModelConstants { public static final String DEVICE_BY_TENANT_AND_NAME_VIEW_NAME = "device_by_tenant_and_name"; public static final String DEVICE_TYPES_BY_TENANT_VIEW_NAME = "device_types_by_tenant"; + /** + * Cassandra audit log constants. + */ + public static final String AUDIT_LOG_COLUMN_FAMILY_NAME = "audit_log"; + + public static final String AUDIT_LOG_BY_ENTITY_ID_CF = "audit_log_by_entity_id"; + public static final String AUDIT_LOG_BY_TENANT_ID_CF = "audit_log_by_tenant_id"; + public static final String AUDIT_LOG_BY_TENANT_ID_PARTITIONS_CF = "audit_log_by_tenant_id_partitions"; + + public static final String AUDIT_LOG_ID_PROPERTY = ID_PROPERTY; + public static final String AUDIT_LOG_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; + public static final String AUDIT_LOG_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY; + public static final String AUDIT_LOG_ENTITY_TYPE_PROPERTY = ENTITY_TYPE_PROPERTY; + public static final String AUDIT_LOG_ENTITY_ID_PROPERTY = ENTITY_ID_COLUMN; + public static final String AUDIT_LOG_ENTITY_NAME_PROPERTY = "entity_name"; + public static final String AUDIT_LOG_USER_ID_PROPERTY = USER_ID_PROPERTY; + public static final String AUDIT_LOG_PARTITION_PROPERTY = "partition"; + public static final String AUDIT_LOG_USER_NAME_PROPERTY = "user_name"; + public static final String AUDIT_LOG_ACTION_TYPE_PROPERTY = "action_type"; + public static final String AUDIT_LOG_ACTION_DATA_PROPERTY = "action_data"; + public static final String AUDIT_LOG_ACTION_STATUS_PROPERTY = "action_status"; + public static final String AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY = "action_failure_details"; + /** * Cassandra asset constants. */ @@ -310,13 +340,6 @@ public class ModelConstants { public static final String TS_KV_PARTITIONS_CF = "ts_kv_partitions_cf"; public static final String TS_KV_LATEST_CF = "ts_kv_latest_cf"; - - public static final String ENTITY_TYPE_COLUMN = ENTITY_TYPE_PROPERTY; - public static final String ENTITY_ID_COLUMN = "entity_id"; - public static final String ATTRIBUTE_TYPE_COLUMN = "attribute_type"; - public static final String ATTRIBUTE_KEY_COLUMN = "attribute_key"; - public static final String LAST_UPDATE_TS_COLUMN = "last_update_ts"; - public static final String PARTITION_COLUMN = "partition"; public static final String KEY_COLUMN = "key"; public static final String TS_COLUMN = "ts"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AuditLogEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AuditLogEntity.java new file mode 100644 index 0000000000..25b6a21727 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AuditLogEntity.java @@ -0,0 +1,137 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.nosql; + +import com.datastax.driver.mapping.annotations.Column; +import com.datastax.driver.mapping.annotations.Table; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.audit.ActionStatus; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.audit.AuditLog; +import org.thingsboard.server.common.data.id.*; +import org.thingsboard.server.dao.model.BaseEntity; +import org.thingsboard.server.dao.model.type.ActionStatusCodec; +import org.thingsboard.server.dao.model.type.ActionTypeCodec; +import org.thingsboard.server.dao.model.type.EntityTypeCodec; +import org.thingsboard.server.dao.model.type.JsonCodec; + +import java.util.UUID; + +import static org.thingsboard.server.dao.model.ModelConstants.*; + +@Table(name = AUDIT_LOG_COLUMN_FAMILY_NAME) +@Data +@NoArgsConstructor +public class AuditLogEntity implements BaseEntity { + + @Column(name = ID_PROPERTY) + private UUID id; + + @Column(name = AUDIT_LOG_TENANT_ID_PROPERTY) + private UUID tenantId; + + @Column(name = AUDIT_LOG_CUSTOMER_ID_PROPERTY) + private UUID customerId; + + @Column(name = AUDIT_LOG_ENTITY_TYPE_PROPERTY, codec = EntityTypeCodec.class) + private EntityType entityType; + + @Column(name = AUDIT_LOG_ENTITY_ID_PROPERTY) + private UUID entityId; + + @Column(name = AUDIT_LOG_ENTITY_NAME_PROPERTY) + private String entityName; + + @Column(name = AUDIT_LOG_USER_ID_PROPERTY) + private UUID userId; + + @Column(name = AUDIT_LOG_USER_NAME_PROPERTY) + private String userName; + + @Column(name = AUDIT_LOG_ACTION_TYPE_PROPERTY, codec = ActionTypeCodec.class) + private ActionType actionType; + + @Column(name = AUDIT_LOG_ACTION_DATA_PROPERTY, codec = JsonCodec.class) + private JsonNode actionData; + + @Column(name = AUDIT_LOG_ACTION_STATUS_PROPERTY, codec = ActionStatusCodec.class) + private ActionStatus actionStatus; + + @Column(name = AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY) + private String actionFailureDetails; + + @Override + public UUID getId() { + return id; + } + + @Override + public void setId(UUID id) { + this.id = id; + } + + public AuditLogEntity(AuditLog auditLog) { + if (auditLog.getId() != null) { + this.id = auditLog.getId().getId(); + } + if (auditLog.getTenantId() != null) { + this.tenantId = auditLog.getTenantId().getId(); + } + if (auditLog.getEntityId() != null) { + this.entityType = auditLog.getEntityId().getEntityType(); + this.entityId = auditLog.getEntityId().getId(); + } + if (auditLog.getCustomerId() != null) { + this.customerId = auditLog.getCustomerId().getId(); + } + if (auditLog.getUserId() != null) { + this.userId = auditLog.getUserId().getId(); + } + this.entityName = auditLog.getEntityName(); + this.userName = auditLog.getUserName(); + this.actionType = auditLog.getActionType(); + this.actionData = auditLog.getActionData(); + this.actionStatus = auditLog.getActionStatus(); + this.actionFailureDetails = auditLog.getActionFailureDetails(); + } + + @Override + public AuditLog toData() { + AuditLog auditLog = new AuditLog(new AuditLogId(id)); + if (tenantId != null) { + auditLog.setTenantId(new TenantId(tenantId)); + } + if (entityId != null & entityType != null) { + auditLog.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId)); + } + if (customerId != null) { + auditLog.setCustomerId(new CustomerId(customerId)); + } + if (userId != null) { + auditLog.setUserId(new UserId(userId)); + } + auditLog.setEntityName(this.entityName); + auditLog.setUserName(this.userName); + auditLog.setActionType(this.actionType); + auditLog.setActionData(this.actionData); + auditLog.setActionStatus(this.actionStatus); + auditLog.setActionFailureDetails(this.actionFailureDetails); + return auditLog; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AuditLogEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AuditLogEntity.java new file mode 100644 index 0000000000..0f7109106b --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AuditLogEntity.java @@ -0,0 +1,132 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.audit.ActionStatus; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.audit.AuditLog; +import org.thingsboard.server.common.data.id.AuditLogId; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.model.BaseEntity; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.*; + +import static org.thingsboard.server.dao.model.ModelConstants.*; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) +@Table(name = ModelConstants.AUDIT_LOG_COLUMN_FAMILY_NAME) +public class AuditLogEntity extends BaseSqlEntity implements BaseEntity { + + @Column(name = AUDIT_LOG_TENANT_ID_PROPERTY) + private String tenantId; + + @Column(name = AUDIT_LOG_CUSTOMER_ID_PROPERTY) + private String customerId; + + @Enumerated(EnumType.STRING) + @Column(name = AUDIT_LOG_ENTITY_TYPE_PROPERTY) + private EntityType entityType; + + @Column(name = AUDIT_LOG_ENTITY_ID_PROPERTY) + private String entityId; + + @Column(name = AUDIT_LOG_ENTITY_NAME_PROPERTY) + private String entityName; + + @Column(name = AUDIT_LOG_USER_ID_PROPERTY) + private String userId; + + @Column(name = AUDIT_LOG_USER_NAME_PROPERTY) + private String userName; + + @Enumerated(EnumType.STRING) + @Column(name = AUDIT_LOG_ACTION_TYPE_PROPERTY) + private ActionType actionType; + + @Type(type = "json") + @Column(name = AUDIT_LOG_ACTION_DATA_PROPERTY) + private JsonNode actionData; + + @Enumerated(EnumType.STRING) + @Column(name = AUDIT_LOG_ACTION_STATUS_PROPERTY) + private ActionStatus actionStatus; + + @Column(name = AUDIT_LOG_ACTION_FAILURE_DETAILS_PROPERTY) + private String actionFailureDetails; + + public AuditLogEntity() { + super(); + } + + public AuditLogEntity(AuditLog auditLog) { + if (auditLog.getId() != null) { + this.setId(auditLog.getId().getId()); + } + if (auditLog.getTenantId() != null) { + this.tenantId = toString(auditLog.getTenantId().getId()); + } + if (auditLog.getCustomerId() != null) { + this.customerId = toString(auditLog.getCustomerId().getId()); + } + if (auditLog.getEntityId() != null) { + this.entityId = toString(auditLog.getEntityId().getId()); + this.entityType = auditLog.getEntityId().getEntityType(); + } + this.entityName = auditLog.getEntityName(); + this.userName = auditLog.getUserName(); + this.actionType = auditLog.getActionType(); + this.actionData = auditLog.getActionData(); + this.actionStatus = auditLog.getActionStatus(); + this.actionFailureDetails = auditLog.getActionFailureDetails(); + } + + @Override + public AuditLog toData() { + AuditLog auditLog = new AuditLog(new AuditLogId(getId())); + auditLog.setCreatedTime(UUIDs.unixTimestamp(getId())); + if (tenantId != null) { + auditLog.setTenantId(new TenantId(toUUID(tenantId))); + } + if (customerId != null) { + auditLog.setCustomerId(new CustomerId(toUUID(customerId))); + } + if (entityId != null) { + auditLog.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), toUUID(entityId).toString())); + } + auditLog.setEntityName(this.entityName); + auditLog.setUserName(this.userName); + auditLog.setActionType(this.actionType); + auditLog.setActionData(this.actionData); + auditLog.setActionStatus(this.actionStatus); + auditLog.setActionFailureDetails(this.actionFailureDetails); + return auditLog; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/ActionStatusCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/ActionStatusCodec.java new file mode 100644 index 0000000000..a207c40c7a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/type/ActionStatusCodec.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.type; + +import com.datastax.driver.extras.codecs.enums.EnumNameCodec; +import org.thingsboard.server.common.data.audit.ActionStatus; + +public class ActionStatusCodec extends EnumNameCodec { + + public ActionStatusCodec() { + super(ActionStatus.class); + } +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/type/ActionTypeCodec.java b/dao/src/main/java/org/thingsboard/server/dao/model/type/ActionTypeCodec.java new file mode 100644 index 0000000000..9f22d5f7d0 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/type/ActionTypeCodec.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.type; + +import com.datastax.driver.extras.codecs.enums.EnumNameCodec; +import org.thingsboard.server.common.data.audit.ActionType; + +public class ActionTypeCodec extends EnumNameCodec { + + public ActionTypeCodec() { + super(ActionType.class); + } +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java new file mode 100644 index 0000000000..7e4fdc1ce2 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java @@ -0,0 +1,44 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.audit; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.dao.model.sql.AuditLogEntity; + +import java.util.List; + +public interface AuditLogRepository extends CrudRepository { + + @Query("SELECT al FROM AuditLogEntity al WHERE al.tenantId = :tenantId " + + "AND al.id > :idOffset ORDER BY al.id") + List findByTenantId(@Param("tenantId") String tenantId, + @Param("idOffset") String idOffset, + Pageable pageable); + + @Query("SELECT al FROM AuditLogEntity al WHERE al.tenantId = :tenantId " + + "AND al.entityType = :entityType " + + "AND al.entityId = :entityId " + + "AND al.id > :idOffset ORDER BY al.id") + List findByTenantIdAndEntityId(@Param("tenantId") String tenantId, + @Param("entityId") String entityId, + @Param("entityType") EntityType entityType, + @Param("idOffset") String idOffset, + Pageable pageable); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java new file mode 100644 index 0000000000..fe7edf8d95 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java @@ -0,0 +1,103 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.audit; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.audit.AuditLog; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.audit.AuditLogDao; +import org.thingsboard.server.dao.model.sql.AuditLogEntity; +import org.thingsboard.server.dao.sql.JpaAbstractDao; +import org.thingsboard.server.dao.util.SqlDao; + +import javax.annotation.PreDestroy; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.Executors; + +import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR; + +@Component +@SqlDao +public class JpaAuditLogDao extends JpaAbstractDao implements AuditLogDao { + + private ListeningExecutorService insertService = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); + + @Autowired + private AuditLogRepository auditLogRepository; + + @Override + protected Class getEntityClass() { + return AuditLogEntity.class; + } + + @Override + protected CrudRepository getCrudRepository() { + return auditLogRepository; + } + + @PreDestroy + void onDestroy() { + insertService.shutdown(); + } + + @Override + public ListenableFuture saveByTenantId(AuditLog auditLog) { + return insertService.submit(() -> { + save(auditLog); + return null; + }); + } + + @Override + public ListenableFuture saveByTenantIdAndEntityId(AuditLog auditLog) { + return insertService.submit(() -> null); + } + + @Override + public ListenableFuture savePartitionsByTenantId(AuditLog auditLog) { + return insertService.submit(() -> null); + } + + @Override + public List findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) { + return DaoUtil.convertDataList( + auditLogRepository.findByTenantIdAndEntityId( + fromTimeUUID(tenantId), + fromTimeUUID(entityId.getId()), + entityId.getEntityType(), + pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), + new PageRequest(0, pageLink.getLimit()))); + } + + @Override + public List findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink) { + return DaoUtil.convertDataList( + auditLogRepository.findByTenantId( + fromTimeUUID(tenantId), + pageLink.getIdOffset() == null ? NULL_UUID_STR : fromTimeUUID(pageLink.getIdOffset()), + new PageRequest(0, pageLink.getLimit()))); + } +} diff --git a/dao/src/main/resources/cassandra/schema.cql b/dao/src/main/resources/cassandra/schema.cql index dda80676e2..6dc2becca9 100644 --- a/dao/src/main/resources/cassandra/schema.cql +++ b/dao/src/main/resources/cassandra/schema.cql @@ -548,3 +548,44 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.event_by_id AS AND event_type IS NOT NULL AND event_uid IS NOT NULL PRIMARY KEY ((tenant_id, entity_type, entity_id), id, event_type, event_uid) WITH CLUSTERING ORDER BY (id ASC, event_type ASC, event_uid ASC); + + +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_entity_id ( + tenant_id timeuuid, + id timeuuid, + customer_id timeuuid, + entity_id timeuuid, + entity_type text, + entity_name text, + user_id timeuuid, + user_name text, + action_type text, + action_data text, + action_status text, + action_failure_details text, + PRIMARY KEY ((tenant_id, entity_id, entity_type), id) +); + +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id ( + tenant_id timeuuid, + id timeuuid, + partition bigint, + customer_id timeuuid, + entity_id timeuuid, + entity_type text, + entity_name text, + user_id timeuuid, + user_name text, + action_type text, + action_data text, + action_status text, + action_failure_details text, + PRIMARY KEY ((tenant_id, partition), id) +); + +CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id_partitions ( + tenant_id timeuuid, + partition bigint, + PRIMARY KEY (( tenant_id ), partition) +) WITH CLUSTERING ORDER BY ( partition ASC ) +AND compaction = { 'class' : 'LeveledCompactionStrategy' }; \ No newline at end of file diff --git a/dao/src/main/resources/sql/schema.sql b/dao/src/main/resources/sql/schema.sql index 26b314c05d..7c0f172432 100644 --- a/dao/src/main/resources/sql/schema.sql +++ b/dao/src/main/resources/sql/schema.sql @@ -47,6 +47,22 @@ CREATE TABLE IF NOT EXISTS asset ( type varchar(255) ); +CREATE TABLE IF NOT EXISTS audit_log ( + id varchar(31) NOT NULL CONSTRAINT audit_log_pkey PRIMARY KEY, + tenant_id varchar(31), + customer_id varchar(31), + entity_id varchar(31), + entity_type varchar(255), + entity_name varchar(255), + user_id varchar(31), + user_name varchar(255), + action_type varchar(255), + action_data varchar(255), + action_status varchar(255), + search_text varchar(255), + action_failure_details varchar +); + CREATE TABLE IF NOT EXISTS attribute_kv ( entity_type varchar(255), entity_id varchar(31), diff --git a/dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java index d936a38fa0..817f6ee5c7 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java @@ -24,7 +24,7 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClassnameFilters({ - "org.thingsboard.server.dao.sql.*AAATest" + "org.thingsboard.server.dao.sql.*THIS_MUST_BE_FIXED_Test" }) public class JpaDaoTestSuite { diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDaoTest.java new file mode 100644 index 0000000000..ed174a746b --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDaoTest.java @@ -0,0 +1,21 @@ +/** + * Copyright © 2016-2017 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.audit; + +import org.thingsboard.server.dao.AbstractJpaDaoTest; + +public class JpaAuditLogDaoTest extends AbstractJpaDaoTest { +} diff --git a/dao/src/test/resources/sql/drop-all-tables.sql b/dao/src/test/resources/sql/drop-all-tables.sql index 49a3774e51..dfdc90fc35 100644 --- a/dao/src/test/resources/sql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/drop-all-tables.sql @@ -1,6 +1,7 @@ DROP TABLE IF EXISTS admin_settings; DROP TABLE IF EXISTS alarm; DROP TABLE IF EXISTS asset; +DROP TABLE IF EXISTS audit_log; DROP TABLE IF EXISTS attribute_kv; DROP TABLE IF EXISTS component_descriptor; DROP TABLE IF EXISTS customer;