diff --git a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/bo/RoleBO.java b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/bo/RoleBO.java
index 901233062..10e707819 100644
--- a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/bo/RoleBO.java
+++ b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/bo/RoleBO.java
@@ -18,6 +18,7 @@
package io.github.pnoker.common.auth.entity.bo;
import io.github.pnoker.common.entity.base.BaseBO;
+import io.github.pnoker.common.entity.common.TenantOwned;
import io.github.pnoker.common.entity.ext.RoleExt;
import io.github.pnoker.common.enums.EnableFlagEnum;
import lombok.AllArgsConstructor;
@@ -38,7 +39,7 @@ import lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
-public class RoleBO extends BaseBO {
+public class RoleBO extends BaseBO implements TenantOwned {
/**
* ID
diff --git a/dc3-common/dc3-common-dal/src/main/java/io/github/pnoker/common/dal/entity/bo/GroupBO.java b/dc3-common/dc3-common-dal/src/main/java/io/github/pnoker/common/dal/entity/bo/GroupBO.java
index 216149db8..5488ec803 100644
--- a/dc3-common/dc3-common-dal/src/main/java/io/github/pnoker/common/dal/entity/bo/GroupBO.java
+++ b/dc3-common/dc3-common-dal/src/main/java/io/github/pnoker/common/dal/entity/bo/GroupBO.java
@@ -18,6 +18,7 @@
package io.github.pnoker.common.dal.entity.bo;
import io.github.pnoker.common.entity.base.BaseBO;
+import io.github.pnoker.common.entity.common.TenantOwned;
import io.github.pnoker.common.enums.EnableFlagEnum;
import io.github.pnoker.common.enums.GroupTypeFlagEnum;
import lombok.AllArgsConstructor;
@@ -38,7 +39,7 @@ import lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
-public class GroupBO extends BaseBO {
+public class GroupBO extends BaseBO implements TenantOwned {
/**
* Parent group ID.
diff --git a/dc3-common/dc3-common-dal/src/main/java/io/github/pnoker/common/dal/entity/bo/LabelBO.java b/dc3-common/dc3-common-dal/src/main/java/io/github/pnoker/common/dal/entity/bo/LabelBO.java
index 253b114a3..4fed34c1e 100644
--- a/dc3-common/dc3-common-dal/src/main/java/io/github/pnoker/common/dal/entity/bo/LabelBO.java
+++ b/dc3-common/dc3-common-dal/src/main/java/io/github/pnoker/common/dal/entity/bo/LabelBO.java
@@ -18,6 +18,7 @@
package io.github.pnoker.common.dal.entity.bo;
import io.github.pnoker.common.entity.base.BaseBO;
+import io.github.pnoker.common.entity.common.TenantOwned;
import io.github.pnoker.common.enums.EnableFlagEnum;
import io.github.pnoker.common.enums.EntityTypeFlagEnum;
import lombok.AllArgsConstructor;
@@ -38,7 +39,7 @@ import lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
-public class LabelBO extends BaseBO {
+public class LabelBO extends BaseBO implements TenantOwned {
/**
* Label name.
diff --git a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DeviceBO.java b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DeviceBO.java
index 9c2b8b9ff..5daa5f31b 100644
--- a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DeviceBO.java
+++ b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DeviceBO.java
@@ -18,6 +18,7 @@
package io.github.pnoker.common.manager.entity.bo;
import io.github.pnoker.common.entity.base.BaseBO;
+import io.github.pnoker.common.entity.common.TenantOwned;
import io.github.pnoker.common.entity.ext.DeviceExt;
import io.github.pnoker.common.enums.EnableFlagEnum;
import lombok.AllArgsConstructor;
@@ -40,7 +41,7 @@ import java.util.List;
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
-public class DeviceBO extends BaseBO {
+public class DeviceBO extends BaseBO implements TenantOwned {
/**
* Device Name
diff --git a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverAttributeBO.java b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverAttributeBO.java
index 8307d39fd..ec53ce627 100644
--- a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverAttributeBO.java
+++ b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverAttributeBO.java
@@ -18,6 +18,7 @@
package io.github.pnoker.common.manager.entity.bo;
import io.github.pnoker.common.entity.base.BaseBO;
+import io.github.pnoker.common.entity.common.TenantOwned;
import io.github.pnoker.common.entity.ext.DriverAttributeExt;
import io.github.pnoker.common.enums.AttributeTypeFlagEnum;
import io.github.pnoker.common.enums.EnableFlagEnum;
@@ -39,7 +40,7 @@ import lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
-public class DriverAttributeBO extends BaseBO {
+public class DriverAttributeBO extends BaseBO implements TenantOwned {
/**
* Name
diff --git a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverAttributeConfigBO.java b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverAttributeConfigBO.java
index 76a98bb9a..584ecc020 100644
--- a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverAttributeConfigBO.java
+++ b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverAttributeConfigBO.java
@@ -18,6 +18,7 @@
package io.github.pnoker.common.manager.entity.bo;
import io.github.pnoker.common.entity.base.BaseBO;
+import io.github.pnoker.common.entity.common.TenantOwned;
import io.github.pnoker.common.entity.ext.JsonExt;
import io.github.pnoker.common.enums.EnableFlagEnum;
import lombok.AllArgsConstructor;
@@ -38,7 +39,7 @@ import lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
-public class DriverAttributeConfigBO extends BaseBO {
+public class DriverAttributeConfigBO extends BaseBO implements TenantOwned {
/**
* ID
diff --git a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverBO.java b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverBO.java
index e07b867bb..4b0ef3d5a 100644
--- a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverBO.java
+++ b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/DriverBO.java
@@ -18,6 +18,7 @@
package io.github.pnoker.common.manager.entity.bo;
import io.github.pnoker.common.entity.base.BaseBO;
+import io.github.pnoker.common.entity.common.TenantOwned;
import io.github.pnoker.common.entity.ext.DriverExt;
import io.github.pnoker.common.enums.DriverTypeFlagEnum;
import io.github.pnoker.common.enums.EnableFlagEnum;
@@ -39,7 +40,7 @@ import lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
-public class DriverBO extends BaseBO {
+public class DriverBO extends BaseBO implements TenantOwned {
/**
* Name
diff --git a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointAttributeBO.java b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointAttributeBO.java
index 33067a46b..9c54b1b14 100644
--- a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointAttributeBO.java
+++ b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointAttributeBO.java
@@ -18,6 +18,7 @@
package io.github.pnoker.common.manager.entity.bo;
import io.github.pnoker.common.entity.base.BaseBO;
+import io.github.pnoker.common.entity.common.TenantOwned;
import io.github.pnoker.common.entity.ext.PointAttributeExt;
import io.github.pnoker.common.enums.AttributeTypeFlagEnum;
import io.github.pnoker.common.enums.EnableFlagEnum;
@@ -39,7 +40,7 @@ import lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
-public class PointAttributeBO extends BaseBO {
+public class PointAttributeBO extends BaseBO implements TenantOwned {
/**
* Name
diff --git a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointAttributeConfigBO.java b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointAttributeConfigBO.java
index b4eec66ae..8f72feff2 100644
--- a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointAttributeConfigBO.java
+++ b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointAttributeConfigBO.java
@@ -18,6 +18,7 @@
package io.github.pnoker.common.manager.entity.bo;
import io.github.pnoker.common.entity.base.BaseBO;
+import io.github.pnoker.common.entity.common.TenantOwned;
import io.github.pnoker.common.entity.ext.JsonExt;
import io.github.pnoker.common.enums.EnableFlagEnum;
import lombok.AllArgsConstructor;
@@ -38,7 +39,7 @@ import lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
-public class PointAttributeConfigBO extends BaseBO {
+public class PointAttributeConfigBO extends BaseBO implements TenantOwned {
/**
* ID
diff --git a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointBO.java b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointBO.java
index 6ea181559..c039cc0cb 100644
--- a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointBO.java
+++ b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/PointBO.java
@@ -18,6 +18,7 @@
package io.github.pnoker.common.manager.entity.bo;
import io.github.pnoker.common.entity.base.BaseBO;
+import io.github.pnoker.common.entity.common.TenantOwned;
import io.github.pnoker.common.entity.ext.PointExt;
import io.github.pnoker.common.enums.EnableFlagEnum;
import io.github.pnoker.common.enums.PointTypeFlagEnum;
@@ -42,7 +43,7 @@ import java.math.BigDecimal;
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
-public class PointBO extends BaseBO {
+public class PointBO extends BaseBO implements TenantOwned {
/**
* Name
diff --git a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/ProfileBO.java b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/ProfileBO.java
index a46e97f71..427455703 100644
--- a/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/ProfileBO.java
+++ b/dc3-common/dc3-common-manager/src/main/java/io/github/pnoker/common/manager/entity/bo/ProfileBO.java
@@ -18,6 +18,7 @@
package io.github.pnoker.common.manager.entity.bo;
import io.github.pnoker.common.entity.base.BaseBO;
+import io.github.pnoker.common.entity.common.TenantOwned;
import io.github.pnoker.common.entity.ext.ProfileExt;
import io.github.pnoker.common.enums.EnableFlagEnum;
import io.github.pnoker.common.enums.ProfileShareFlagEnum;
@@ -40,7 +41,7 @@ import lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
-public class ProfileBO extends BaseBO {
+public class ProfileBO extends BaseBO implements TenantOwned {
/**
* Name
diff --git a/dc3-common/dc3-common-public/src/main/java/io/github/pnoker/common/entity/common/TenantOwned.java b/dc3-common/dc3-common-public/src/main/java/io/github/pnoker/common/entity/common/TenantOwned.java
new file mode 100644
index 000000000..7e2bf2bcf
--- /dev/null
+++ b/dc3-common/dc3-common-public/src/main/java/io/github/pnoker/common/entity/common/TenantOwned.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016-present the IoT DC3 original author or authors.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package io.github.pnoker.common.entity.common;
+
+/**
+ * Marker contract for tenant-scoped objects.
+ *
+ * @author pnoker
+ * @version 2026.5.10
+ * @since 2026.5.10
+ */
+public interface TenantOwned {
+
+ Long getTenantId();
+
+}
diff --git a/dc3-common/dc3-common-web/src/main/java/io/github/pnoker/common/base/BaseController.java b/dc3-common/dc3-common-web/src/main/java/io/github/pnoker/common/base/BaseController.java
index 9b855e123..722de536c 100644
--- a/dc3-common/dc3-common-web/src/main/java/io/github/pnoker/common/base/BaseController.java
+++ b/dc3-common/dc3-common-web/src/main/java/io/github/pnoker/common/base/BaseController.java
@@ -19,12 +19,12 @@ package io.github.pnoker.common.base;
import io.github.pnoker.common.entity.R;
import io.github.pnoker.common.entity.common.RequestHeader;
+import io.github.pnoker.common.entity.common.TenantOwned;
import io.github.pnoker.common.exception.NotFoundException;
import io.github.pnoker.common.utils.UserHeaderUtil;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
-import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
@@ -65,8 +65,8 @@ public interface BaseController {
* Fail closed when an ID-based lookup returns an entity outside the caller's tenant.
* Returning 404 avoids exposing whether a cross-tenant resource exists.
*/
- default T requireTenant(Long tenantId, T entity) {
- if (Objects.isNull(entity) || !Objects.equals(tenantId, resolveTenantId(entity))) {
+ default T requireTenant(Long tenantId, T entity) {
+ if (Objects.isNull(entity) || !Objects.equals(tenantId, entity.getTenantId())) {
throw new NotFoundException("Resource does not exist");
}
return entity;
@@ -75,26 +75,16 @@ public interface BaseController {
/**
* Keep only entities that belong to the caller's tenant for bulk lookup endpoints.
*/
- default List filterTenant(Long tenantId, Collection entities) {
+ default List filterTenant(Long tenantId, Collection entities) {
if (Objects.isNull(entities) || entities.isEmpty()) {
return List.of();
}
return entities.stream()
.filter(Objects::nonNull)
- .filter(entity -> Objects.equals(tenantId, resolveTenantId(entity)))
+ .filter(entity -> Objects.equals(tenantId, entity.getTenantId()))
.toList();
}
- private Long resolveTenantId(Object entity) {
- try {
- Method method = entity.getClass().getMethod("getTenantId");
- Object tenantId = method.invoke(entity);
- return tenantId instanceof Long value ? value : null;
- } catch (ReflectiveOperationException e) {
- throw new NotFoundException("Resource does not exist");
- }
- }
-
/**
* Get user ID from user header
*