Browse Source

Added audit log base impl

pull/585/head
Volodymyr Babak 9 years ago
parent
commit
3898e70cb2
  1. 70
      application/src/main/java/org/thingsboard/server/controller/AuditLogController.java
  2. 4
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  3. 35
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  4. 30
      application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
  5. 118
      application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java
  6. 23
      application/src/test/java/org/thingsboard/server/controller/nosql/AuditLogControllerNoSqlTest.java
  7. 23
      application/src/test/java/org/thingsboard/server/controller/sql/AuditLogControllerSqlTest.java
  8. 20
      common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionStatus.java
  9. 20
      common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
  10. 60
      common/data/src/main/java/org/thingsboard/server/common/data/audit/AuditLog.java
  11. 35
      common/data/src/main/java/org/thingsboard/server/common/data/id/AuditLogId.java
  12. 37
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java
  13. 50
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java
  14. 130
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
  15. 242
      dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java
  16. 37
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  17. 137
      dao/src/main/java/org/thingsboard/server/dao/model/nosql/AuditLogEntity.java
  18. 132
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AuditLogEntity.java
  19. 26
      dao/src/main/java/org/thingsboard/server/dao/model/type/ActionStatusCodec.java
  20. 26
      dao/src/main/java/org/thingsboard/server/dao/model/type/ActionTypeCodec.java
  21. 44
      dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java
  22. 103
      dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java
  23. 41
      dao/src/main/resources/cassandra/schema.cql
  24. 16
      dao/src/main/resources/sql/schema.sql
  25. 2
      dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java
  26. 21
      dao/src/test/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDaoTest.java
  27. 1
      dao/src/test/resources/sql/drop-all-tables.sql

70
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<AuditLog> 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<AuditLog> 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);
}
}
}

4
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) {

35
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);
}
}

30
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> T doGetTypedWithTimePageLink(String urlTemplate, TypeReference<T> responseType,
TimePageLink pageLink,
Object... urlVariables) throws Exception {
List<Object> 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> T doPost(String urlTemplate, Class<T> responseClass, String... params) throws Exception {
return readResponse(doPost(urlTemplate, params).andExpect(status().isOk()), responseClass);
}

118
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<AuditLog> loadedAuditLogs = new ArrayList<>();
TimePageLink pageLink = new TimePageLink(23);
TimePageData<AuditLog> pageData;
do {
pageData = doGetTypedWithTimePageLink("/api/audit/logs?",
new TypeReference<TimePageData<AuditLog>>() {
}, 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<AuditLog> loadedAuditLogs = new ArrayList<>();
TimePageLink pageLink = new TimePageLink(23);
TimePageData<AuditLog> pageData;
do {
pageData = doGetTypedWithTimePageLink("/api/audit/logs/DEVICE/" + savedDevice.getId().getId() + "?",
new TypeReference<TimePageData<AuditLog>>() {
}, pageLink);
loadedAuditLogs.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
Assert.assertEquals(179, loadedAuditLogs.size());
}
}

23
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 {
}

23
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 {
}

20
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
}

20
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
}

60
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<AuditLogId> {
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();
}
}

35
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));
}
}

37
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<Void> saveByTenantId(AuditLog auditLog);
ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog);
ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog);
List<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink);
List<AuditLog> findAuditLogsByTenantId(UUID tenantId, TimePageLink pageLink);
}

50
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<AuditLog> findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, TimePageLink pageLink);
TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink);
ListenableFuture<List<Void>> logAction(TenantId tenantId,
EntityId entityId,
String entityName,
CustomerId customerId,
UserId userId,
String userName,
ActionType actionType,
JsonNode actionData,
ActionStatus actionStatus,
String actionFailureDetails);
}

130
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<AuditLog> 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<AuditLog> auditLogs = auditLogDao.findAuditLogsByTenantIdAndEntityId(tenantId.getId(), entityId, pageLink);
return new TimePageData<>(auditLogs, pageLink);
}
@Override
public TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink) {
log.trace("Executing findAuditLogs [{}]", pageLink);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
List<AuditLog> 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<List<Void>> 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<ListenableFuture<Void>> 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<AuditLog> auditLogValidator =
new DataValidator<AuditLog>() {
@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!");
}
}
};
}

242
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<AuditLogEntity, AuditLog> implements AuditLogDao {
private static final String INSERT_INTO = "INSERT INTO ";
@Autowired
private Environment environment;
@Override
protected Class<AuditLogEntity> 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<TsPartitionDate> 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 <T> ListenableFuture<T> getFuture(ResultSetFuture future, java.util.function.Function<ResultSet, T> transformer) {
return Futures.transform(future, new Function<ResultSet, T>() {
@Nullable
@Override
public T apply(@Nullable ResultSet input) {
return transformer.apply(input);
}
}, readResultsProcessingExecutor);
}
@Override
public ListenableFuture<Void> 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<Void> 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<Void> 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<AuditLog> findAuditLogsByTenantIdAndEntityId(UUID tenantId, EntityId entityId, TimePageLink pageLink) {
log.trace("Try to find audit logs by tenant [{}], entity [{}] and pageLink [{}]", tenantId, entityId, pageLink);
List<AuditLogEntity> 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<AuditLog> 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<AuditLogEntity> 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);
}
}

37
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";

137
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<AuditLog> {
@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;
}
}

132
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<AuditLog> implements BaseEntity<AuditLog> {
@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;
}
}

26
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<ActionStatus> {
public ActionStatusCodec() {
super(ActionStatus.class);
}
}

26
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<ActionType> {
public ActionTypeCodec() {
super(ActionType.class);
}
}

44
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<AuditLogEntity, String> {
@Query("SELECT al FROM AuditLogEntity al WHERE al.tenantId = :tenantId " +
"AND al.id > :idOffset ORDER BY al.id")
List<AuditLogEntity> 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<AuditLogEntity> findByTenantIdAndEntityId(@Param("tenantId") String tenantId,
@Param("entityId") String entityId,
@Param("entityType") EntityType entityType,
@Param("idOffset") String idOffset,
Pageable pageable);
}

103
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<AuditLogEntity, AuditLog> implements AuditLogDao {
private ListeningExecutorService insertService = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
@Autowired
private AuditLogRepository auditLogRepository;
@Override
protected Class<AuditLogEntity> getEntityClass() {
return AuditLogEntity.class;
}
@Override
protected CrudRepository<AuditLogEntity, String> getCrudRepository() {
return auditLogRepository;
}
@PreDestroy
void onDestroy() {
insertService.shutdown();
}
@Override
public ListenableFuture<Void> saveByTenantId(AuditLog auditLog) {
return insertService.submit(() -> {
save(auditLog);
return null;
});
}
@Override
public ListenableFuture<Void> saveByTenantIdAndEntityId(AuditLog auditLog) {
return insertService.submit(() -> null);
}
@Override
public ListenableFuture<Void> savePartitionsByTenantId(AuditLog auditLog) {
return insertService.submit(() -> null);
}
@Override
public List<AuditLog> 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<AuditLog> 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())));
}
}

41
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' };

16
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),

2
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 {

21
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 {
}

1
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;

Loading…
Cancel
Save