thingsboard-aplcache
Changes
application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java 118(+118 -0)
application/src/test/java/org/thingsboard/server/controller/nosql/AuditLogControllerNoSqlTest.java 23(+23 -0)
application/src/test/java/org/thingsboard/server/controller/sql/AuditLogControllerSqlTest.java 23(+23 -0)
dao/src/main/resources/cassandra/schema.cql 41(+41 -0)
dao/src/main/resources/sql/schema.sql 16(+16 -0)
Details
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 0000000..b79359b
--- /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<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);
+ }
+ }
+}
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 16ba93e..ed5975a 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 eeb10c8..8d08706 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 19e4329..36d736d 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> 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);
}
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 0000000..5e74416
--- /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<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());
+ }
+}
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 0000000..4692afe
--- /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 0000000..df6804e
--- /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 0000000..5ee8beb
--- /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 0000000..495f80d
--- /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 0000000..62d4bfa
--- /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<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();
+ }
+}
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 0000000..5327212
--- /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 0000000..f401ad1
--- /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<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);
+}
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 0000000..5593ede
--- /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<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);
+
+
+}
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 0000000..1baf573
--- /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<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!");
+ }
+ }
+ };
+}
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 0000000..2fcb732
--- /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<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);
+ }
+}
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 9b596fc..58c8d2f 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.
*/
@@ -135,6 +142,29 @@ public class ModelConstants {
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.
*/
public static final String ASSET_COLUMN_FAMILY_NAME = "asset";
@@ -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 0000000..25b6a21
--- /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<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;
+ }
+}
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 0000000..0f71091
--- /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<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;
+ }
+}
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 0000000..a207c40
--- /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<ActionStatus> {
+
+ 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 0000000..9f22d5f
--- /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<ActionType> {
+
+ 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 0000000..7e4fdc1
--- /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<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);
+}
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 0000000..fe7edf8
--- /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<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())));
+ }
+}
dao/src/main/resources/cassandra/schema.cql 41(+41 -0)
diff --git a/dao/src/main/resources/cassandra/schema.cql b/dao/src/main/resources/cassandra/schema.cql
index dda8067..6dc2bec 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
dao/src/main/resources/sql/schema.sql 16(+16 -0)
diff --git a/dao/src/main/resources/sql/schema.sql b/dao/src/main/resources/sql/schema.sql
index 26b314c..7c0f172 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 d936a38..817f6ee 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 0000000..ed174a7
--- /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 49a3774..dfdc90f 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;