node result retrive with both nodes and edges

backup
Isuri Mendis 1 year ago
parent 826912c8a6
commit 1010baf33a

@ -21,9 +21,27 @@ import io.entgra.device.mgt.core.apimgt.annotations.Scope;
import io.entgra.device.mgt.core.apimgt.annotations.Scopes;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.api.beans.ErrorResponse;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceOrganization;
import io.swagger.annotations.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Extension;
import io.swagger.annotations.ExtensionProperty;
import io.swagger.annotations.Info;
import io.swagger.annotations.ResponseHeader;
import io.swagger.annotations.SwaggerDefinition;
import io.swagger.annotations.Tag;
import javax.ws.rs.*;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@ -277,7 +295,7 @@ public interface DeviceOrganizationMgtService {
* @return A response containing a list of leaf device organizations.
*/
@GET
@Path("/leafs")
@Path("leafs")
@ApiOperation(
produces = MediaType.APPLICATION_JSON,
httpMethod = "GET",
@ -345,7 +363,7 @@ public interface DeviceOrganizationMgtService {
* @return A response containing a list of root device organizations.
*/
@GET
@Path("/roots")
@Path("roots")
@ApiOperation(
produces = MediaType.APPLICATION_JSON,
httpMethod = "GET",

@ -20,7 +20,7 @@ package io.entgra.device.mgt.core.device.mgt.extensions.device.organization.api;
import com.google.gson.Gson;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.api.util.DeviceOrgAPIUtils;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.api.util.RequestValidationUtil;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceNode;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceNodeResult;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceOrganization;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.PaginationRequest;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.exception.DeviceOrganizationMgtPluginException;
@ -28,7 +28,15 @@ import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.spi.D
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.ws.rs.*;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
@ -74,9 +82,7 @@ public class DeviceOrganizationMgtServiceImpl implements DeviceOrganizationMgtSe
@QueryParam("includeDevice") boolean includeDevice) {
try {
DeviceOrganizationService deviceOrganizationService = DeviceOrgAPIUtils.getDeviceOrganizationService();
DeviceNode deviceNode = new DeviceNode();
deviceNode.setDeviceId(deviceId);
List<DeviceNode> children = deviceOrganizationService.getChildrenOfDeviceNode(deviceNode, maxDepth, includeDevice);
DeviceNodeResult children = deviceOrganizationService.getChildrenOfDeviceNode(deviceId, maxDepth, includeDevice);
return Response.status(Response.Status.OK).entity(children).build();
} catch (DeviceOrganizationMgtPluginException e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
@ -92,9 +98,7 @@ public class DeviceOrganizationMgtServiceImpl implements DeviceOrganizationMgtSe
@QueryParam("includeDevice") boolean includeDevice) {
try {
DeviceOrganizationService deviceOrganizationService = DeviceOrgAPIUtils.getDeviceOrganizationService();
DeviceNode deviceNode = new DeviceNode();
deviceNode.setDeviceId(deviceId);
List<DeviceNode> parents = deviceOrganizationService.getParentsOfDeviceNode(deviceNode, maxDepth, includeDevice);
DeviceNodeResult parents = deviceOrganizationService.getParentsOfDeviceNode(deviceId, maxDepth, includeDevice);
return Response.status(Response.Status.OK).entity(parents).build();
} catch (DeviceOrganizationMgtPluginException e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();

@ -17,7 +17,7 @@
*/
package io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dao;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceNode;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceNodeResult;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceOrganization;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.PaginationRequest;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.exception.DeviceOrganizationMgtDAOException;
@ -32,24 +32,24 @@ public interface DeviceOrganizationDAO {
/**
* Retrieves child devices per particular device ID
*
* @param node The device node for which child devices are retrieved.
* @param deviceId The device ID for which child devices are retrieved.
* @param maxDepth The maximum depth to traverse when fetching child devices.
* @param includeDevice Flag to indicate whether to include the parent device in the result.
* @return A list of child device nodes.
* @throws DeviceOrganizationMgtDAOException If an error occurs while retrieving child devices.
*/
List<DeviceNode> getChildrenOfDeviceNode(DeviceNode node, int maxDepth, boolean includeDevice) throws DeviceOrganizationMgtDAOException;
DeviceNodeResult getChildrenOfDeviceNode(int deviceId, int maxDepth, boolean includeDevice) throws DeviceOrganizationMgtDAOException;
/**
* Retrieves parent devices for a given device node.
*
* @param node The device node for which parent devices are retrieved.
* @param deviceId The device ID for which parent devices are retrieved.
* @param maxDepth The maximum depth to traverse when fetching parent devices.
* @param includeDevice Flag to indicate whether to include the current device node in the result.
* @return A list of parent device nodes.
* @throws DeviceOrganizationMgtDAOException If an error occurs while retrieving parent devices.
*/
List<DeviceNode> getParentsOfDeviceNode(DeviceNode node, int maxDepth, boolean includeDevice) throws DeviceOrganizationMgtDAOException;
DeviceNodeResult getParentsOfDeviceNode(int deviceId, int maxDepth, boolean includeDevice) throws DeviceOrganizationMgtDAOException;
/**
* Retrieves all device organization records.
@ -61,6 +61,7 @@ public interface DeviceOrganizationDAO {
/**
* Retrieves device Organization Roots
*
* @return A list of root device organization records.
* @throws DeviceOrganizationMgtDAOException
*/
@ -68,6 +69,7 @@ public interface DeviceOrganizationDAO {
/**
* Retrieves device Organization Leafs
*
* @return A list of leaf device organization records.
* @throws DeviceOrganizationMgtDAOException
*/

@ -20,6 +20,7 @@ package io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dao.
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dao.DeviceOrganizationDAO;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dao.util.ConnectionManagerUtil;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceNode;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceNodeResult;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceOrganization;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.PaginationRequest;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.exception.DBConnectionException;
@ -53,63 +54,74 @@ public class DeviceOrganizationDAOImpl implements DeviceOrganizationDAO {
* {@inheritDoc}
*/
@Override
public List<DeviceNode> getChildrenOfDeviceNode(DeviceNode node, int maxDepth, boolean includeDevice)
public DeviceNodeResult getChildrenOfDeviceNode(int deviceId, int maxDepth, boolean includeDevice)
throws DeviceOrganizationMgtDAOException {
List<DeviceNode> childNodes = new ArrayList<>();
Set<DeviceOrganization> organizations = new HashSet<>();
Set<Integer> visited = new HashSet<>();
Set<Integer> twiseVisited = new HashSet<>();
Set<Integer> twiceVisited = new HashSet<>();
try {
Connection conn = ConnectionManagerUtil.getDBConnection();
DeviceNode deviceNode = getDeviceDetails(deviceId, conn);
boolean parentAdded = false; // Flag to track whether the parent device has been added
getChildrenRecursive(node, maxDepth,
getChildrenRecursive(
deviceNode,
maxDepth,
visited,
twiseVisited,
conn, childNodes, includeDevice
, parentAdded
twiceVisited,
conn,
childNodes,
includeDevice,
parentAdded,
organizations
);
if (!includeDevice
&& !parentAdded
) {
childNodes.add(node); // Add the parent device if it hasn't been added and includeDevice is false.
childNodes.add(deviceNode); // Add the parent device if it hasn't been added and includeDevice is false.
}
return childNodes;
return new DeviceNodeResult(childNodes, organizations);
} catch (DBConnectionException e) {
String msg = "Error occurred while obtaining DB connection to retrieve all child devices for " +
"parent device ID " + node.getDeviceId();
"parent device ID " + deviceId;
log.error(msg);
throw new DeviceOrganizationMgtDAOException(msg, e);
} catch (SQLException e) {
String msg = "Error occurred while processing SQL to retrieve all child devices for " +
"parent device ID " + node.getDeviceId();
"parent device ID " + deviceId;
log.error(msg);
throw new DeviceOrganizationMgtDAOException(msg, e);
}
}
private void getChildrenRecursive(DeviceNode node, int maxDepth,
private void getChildrenRecursive(DeviceNode node,
int maxDepth,
Set<Integer> visited,
Set<Integer> twiseVisited,
Set<Integer> twiceVisited,
Connection conn,
List<DeviceNode> childNodes, boolean includeDevice
, boolean parentAdded
List<DeviceNode> childNodes,
boolean includeDevice,
boolean parentAdded,
Set<DeviceOrganization> organizations
)
throws SQLException {
if (maxDepth <= 0) {
return;
}
if (twiseVisited.contains(node.getDeviceId())) {
if (twiceVisited.contains(node.getDeviceId())) {
return;
}
if (visited.contains(node.getDeviceId())) {
twiseVisited.add(node.getDeviceId());
twiceVisited.add(node.getDeviceId());
}
visited.add(node.getDeviceId());
String sql = "SELECT D.ID, D.NAME, D.DESCRIPTION, D.DEVICE_IDENTIFICATION, DT.NAME AS DEVICE_TYPE_NAME " +
"FROM DM_DEVICE D " +
String sql = "SELECT D.ID, D.NAME, D.DESCRIPTION, D.DEVICE_IDENTIFICATION, DT.NAME AS DEVICE_TYPE_NAME, " +
"DO.ORGANIZATION_ID, DO.DEVICE_ID, DO.PARENT_DEVICE_ID, DO.DEVICE_ORGANIZATION_META ," +
"DO.LAST_UPDATED_TIMESTAMP FROM DM_DEVICE D " +
"JOIN DM_DEVICE_ORGANIZATION DO ON D.ID = DO.DEVICE_ID " +
"JOIN DM_DEVICE_TYPE DT ON D.DEVICE_TYPE_ID = DT.ID " +
"WHERE DO.PARENT_DEVICE_ID = ?";
@ -128,11 +140,19 @@ public class DeviceOrganizationDAOImpl implements DeviceOrganizationDAO {
parentAdded = true; // Set the flag to true after adding the parent device.
}
getChildrenRecursive(child, maxDepth - 1,
DeviceOrganization organization = loadDeviceOrganization(rs);
organizations.add(organization);
getChildrenRecursive(
child,
(maxDepth - 1),
visited,
twiseVisited,
conn, childNodes, includeDevice
, parentAdded
twiceVisited,
conn,
childNodes,
includeDevice,
parentAdded,
organizations
);
}
}
@ -143,56 +163,71 @@ public class DeviceOrganizationDAOImpl implements DeviceOrganizationDAO {
* {@inheritDoc}
*/
@Override
public List<DeviceNode> getParentsOfDeviceNode(DeviceNode node, int maxDepth, boolean includeDevice)
public DeviceNodeResult getParentsOfDeviceNode(int deviceId, int maxDepth, boolean includeDevice)
throws DeviceOrganizationMgtDAOException {
List<DeviceNode> parentNodes = new ArrayList<>();
Set<DeviceOrganization> organizations = new HashSet<>();
Set<Integer> visited = new HashSet<>();
Set<Integer> twiseVisited = new HashSet<>();
Set<Integer> twiceVisited = new HashSet<>();
try {
Connection conn = ConnectionManagerUtil.getDBConnection();
DeviceNode deviceNode = getDeviceDetails(deviceId, conn);
boolean childAdded = false;
getParentsRecursive(node, maxDepth,
getParentsRecursive(
deviceNode,
maxDepth,
visited,
twiseVisited,
conn, parentNodes, includeDevice, childAdded);
twiceVisited,
conn,
parentNodes,
includeDevice,
childAdded,
organizations);
if (!includeDevice && !childAdded) {
parentNodes.add(node);
parentNodes.add(deviceNode);
}
return parentNodes;
return new DeviceNodeResult(parentNodes, organizations);
} catch (DBConnectionException e) {
String msg = "Error occurred while obtaining DB connection to retrieve parent devices for " +
"device ID " + node.getDeviceId();
"device ID " + deviceId;
log.error(msg);
throw new DeviceOrganizationMgtDAOException(msg, e);
} catch (SQLException e) {
String msg = "Error occurred while processing SQL to retrieve parent devices for " +
"device ID " + node.getDeviceId();
"device ID " + deviceId;
log.error(msg);
throw new DeviceOrganizationMgtDAOException(msg, e);
}
}
private void getParentsRecursive(DeviceNode node, int maxDepth,
private void getParentsRecursive(DeviceNode node,
int maxDepth,
Set<Integer> visited,
Set<Integer> twiseVisited,
Set<Integer> twiceVisited,
Connection conn,
List<DeviceNode> parentNodes, boolean includeDevice, boolean childAdded) throws SQLException {
List<DeviceNode> parentNodes,
boolean includeDevice,
boolean childAdded,
Set<DeviceOrganization> organizations)
throws SQLException {
if (maxDepth <= 0) {
return;
}
if (twiseVisited.contains(node.getDeviceId())) {
if (twiceVisited.contains(node.getDeviceId())) {
return;
}
if (visited.contains(node.getDeviceId())) {
twiseVisited.add(node.getDeviceId());
twiceVisited.add(node.getDeviceId());
}
visited.add(node.getDeviceId());
String sql = "SELECT D.ID, D.NAME, D.DESCRIPTION, D.DEVICE_IDENTIFICATION, DT.NAME AS DEVICE_TYPE_NAME " +
"FROM DM_DEVICE D " +
String sql = "SELECT D.ID, D.NAME, D.DESCRIPTION, D.DEVICE_IDENTIFICATION, DT.NAME AS DEVICE_TYPE_NAME, " +
"DO.ORGANIZATION_ID, DO.DEVICE_ID, DO.PARENT_DEVICE_ID, DO.DEVICE_ORGANIZATION_META ," +
"DO.LAST_UPDATED_TIMESTAMP FROM DM_DEVICE D " +
"JOIN DM_DEVICE_ORGANIZATION DO ON D.ID = DO.PARENT_DEVICE_ID " +
"JOIN DM_DEVICE_TYPE DT ON D.DEVICE_TYPE_ID = DT.ID " +
"WHERE DO.DEVICE_ID = ?";
@ -207,15 +242,42 @@ public class DeviceOrganizationDAOImpl implements DeviceOrganizationDAO {
parentNodes.add(node);
childAdded = true;
}
getParentsRecursive(parent, maxDepth - 1,
DeviceOrganization organization = loadDeviceOrganization(rs);
organizations.add(organization);
getParentsRecursive(
parent,
(maxDepth - 1),
visited,
twiseVisited,
conn, parentNodes, includeDevice, childAdded);
twiceVisited,
conn,
parentNodes,
includeDevice,
childAdded,
organizations);
}
}
}
}
private DeviceNode getDeviceDetails(int deviceId, Connection conn) throws SQLException {
String sql = "SELECT D.ID, D.NAME, D.DESCRIPTION, D.DEVICE_IDENTIFICATION, DT.NAME AS DEVICE_TYPE_NAME " +
"FROM DM_DEVICE D " +
"JOIN DM_DEVICE_TYPE DT ON D.DEVICE_TYPE_ID = DT.ID " +
"WHERE D.ID = ?";
DeviceNode node = new DeviceNode();
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, deviceId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
node = getDeviceFromResultSet(rs);
}
}
}
return node;
}
/**
* {@inheritDoc}
*/
@ -286,8 +348,9 @@ public class DeviceOrganizationDAOImpl implements DeviceOrganizationDAO {
List<DeviceOrganization> deviceOrganizations = new ArrayList<>();
try {
Connection conn = ConnectionManagerUtil.getDBConnection();
String sql = "SELECT * FROM DM_DEVICE_ORGANIZATION WHERE ID NOT IN " +
"(SELECT DISTINCT PARENT_DEVICE_ID FROM DM_DEVICE_ORGANIZATION WHERE PARENT_DEVICE_ID IS NOT NULL OR ID IS NOT NULL) " +
String sql = "SELECT * FROM DM_DEVICE_ORGANIZATION WHERE ORGANIZATION_ID NOT IN " +
"(SELECT DISTINCT PARENT_DEVICE_ID FROM DM_DEVICE_ORGANIZATION WHERE PARENT_DEVICE_ID IS NOT NULL " +
"OR ORGANIZATION_ID IS NOT NULL) " +
"LIMIT ? OFFSET ?";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, request.getLimit());
@ -463,7 +526,7 @@ public class DeviceOrganizationDAOImpl implements DeviceOrganizationDAO {
if (!Objects.equals(organization.getDeviceOrganizationMeta(), deviceOrganization.getDeviceOrganizationMeta())) {
sql += "DEVICE_ORGANIZATION_META = ? ,";
}
sql += "LAST_UPDATED_TIMESTAMP = ? WHERE ID = ? ";
sql += "LAST_UPDATED_TIMESTAMP = ? WHERE ORGANIZATION_ID = ? ";
Connection conn = ConnectionManagerUtil.getDBConnection();
Calendar calendar = Calendar.getInstance();
@ -504,16 +567,16 @@ public class DeviceOrganizationDAOImpl implements DeviceOrganizationDAO {
@Override
public DeviceOrganization getDeviceOrganizationByID(int organizationId) throws DeviceOrganizationMgtDAOException {
try {
String sql = "SELECT * FROM DM_DEVICE_ORGANIZATION do WHERE do.ID = ? ";
Connection conn = ConnectionManagerUtil.getDBConnection();
String sql = "SELECT * FROM DM_DEVICE_ORGANIZATION do WHERE do.ORGANIZATION_ID = ? ";
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, organizationId);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return loadDeviceOrganization(rs);
}
log.info("No Device Organization found for retrieval for organizationID = " + organizationId);
log.info("No Device Organization found for retrieving for organizationID = " + organizationId);
return null;
}
}
@ -538,7 +601,7 @@ public class DeviceOrganizationDAOImpl implements DeviceOrganizationDAO {
public boolean deleteDeviceOrganizationByID(int organizationId) throws DeviceOrganizationMgtDAOException {
try {
Connection conn = ConnectionManagerUtil.getDBConnection();
String deleteOrganizationSql = "DELETE FROM DM_DEVICE_ORGANIZATION WHERE ID = ?";
String deleteOrganizationSql = "DELETE FROM DM_DEVICE_ORGANIZATION WHERE ORGANIZATION_ID = ?";
try (PreparedStatement deleteOrgStmt = conn.prepareStatement(deleteOrganizationSql)) {

@ -26,7 +26,7 @@ public class DeviceOrganizationDaoUtil {
*/
public static DeviceOrganization loadDeviceOrganization(ResultSet rs) throws SQLException {
DeviceOrganization deviceOrganization = new DeviceOrganization();
deviceOrganization.setOrganizationId(rs.getInt("ID"));
deviceOrganization.setOrganizationId(rs.getInt("ORGANIZATION_ID"));
deviceOrganization.setDeviceId(rs.getInt("DEVICE_ID"));
if (rs.getInt("PARENT_DEVICE_ID") != 0) {
deviceOrganization.setParentDeviceId(rs.getInt("PARENT_DEVICE_ID"));

@ -0,0 +1,24 @@
package io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto;
import java.util.List;
import java.util.Set;
public class DeviceNodeResult {
private List<DeviceNode> nodes;
private Set<DeviceOrganization> edges;
public DeviceNodeResult(List<DeviceNode> nodes, Set<DeviceOrganization> edges) {
this.nodes = nodes;
this.edges = edges;
}
public List<DeviceNode> getNodes() {
return nodes;
}
public Set<DeviceOrganization> getEdges() {
return edges;
}
}

@ -20,6 +20,7 @@ package io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto;
import java.util.Date;
import java.util.Objects;
/**
* This abstract class represents a device organization entity used in DeviceOrganizationService.
@ -72,4 +73,22 @@ public class DeviceOrganization {
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DeviceOrganization that = (DeviceOrganization) o;
// Compare fields for equality
return Objects.equals(organizationId, that.organizationId)
&& Objects.equals(deviceId, that.deviceId)
&& Objects.equals(parentDeviceId, that.parentDeviceId)
&& Objects.equals(deviceOrganizationMeta, that.deviceOrganizationMeta);
}
@Override
public int hashCode() {
// Hash based on fields
return Objects.hash(organizationId, deviceId, parentDeviceId, deviceOrganizationMeta);
}
}

@ -20,7 +20,7 @@ package io.entgra.device.mgt.core.device.mgt.extensions.device.organization.impl
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dao.DeviceOrganizationDAO;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dao.DeviceOrganizationDAOFactory;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dao.util.ConnectionManagerUtil;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceNode;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceNodeResult;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceOrganization;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.PaginationRequest;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.exception.BadRequestException;
@ -47,27 +47,28 @@ public class DeviceOrganizationServiceImpl implements DeviceOrganizationService
* {@inheritDoc}
*/
@Override
public List<DeviceNode> getChildrenOfDeviceNode(DeviceNode node, int maxDepth, boolean includeDevice)
public DeviceNodeResult getChildrenOfDeviceNode(int deviceId, int maxDepth, boolean includeDevice)
throws DeviceOrganizationMgtPluginException {
if (node == null || node.getDeviceId() <= 0 || maxDepth < 0) {
if (deviceId <= 0 || maxDepth < 0) {
String msg = "Invalid input parameters for retrieving child devices : " +
"deviceID = " + (node != null ? node.getDeviceId() : null) + ", maxDepth = " + maxDepth +
"deviceID = " + deviceId + ", maxDepth = " + maxDepth +
", includeDevice = " + includeDevice;
throw new BadRequestException(msg);
}
try {
// Open a database connection
ConnectionManagerUtil.openDBConnection();
return deviceOrganizationDao.getChildrenOfDeviceNode(node, maxDepth, includeDevice);
//set device details
return deviceOrganizationDao.getChildrenOfDeviceNode(deviceId, maxDepth, includeDevice);
} catch (DBConnectionException e) {
String msg = "Error occurred while obtaining the database connection to retrieve child devices : " +
"deviceID = " + node.getDeviceId() + ", maxDepth = " + maxDepth + ", includeDevice = " +
"deviceID = " + deviceId + ", maxDepth = " + maxDepth + ", includeDevice = " +
includeDevice;
log.error(msg);
throw new DeviceOrganizationMgtPluginException(msg, e);
} catch (DeviceOrganizationMgtDAOException e) {
String msg = "Error occurred in the database level while retrieving child devices : " +
"deviceID = " + node.getDeviceId() + ", maxDepth = " + maxDepth + ", includeDevice = " +
"deviceID = " + deviceId + ", maxDepth = " + maxDepth + ", includeDevice = " +
includeDevice;
log.error(msg);
throw new DeviceOrganizationMgtPluginException(msg, e);
@ -81,27 +82,27 @@ public class DeviceOrganizationServiceImpl implements DeviceOrganizationService
* {@inheritDoc}
*/
@Override
public List<DeviceNode> getParentsOfDeviceNode(DeviceNode node, int maxDepth, boolean includeDevice)
public DeviceNodeResult getParentsOfDeviceNode(int deviceId, int maxDepth, boolean includeDevice)
throws DeviceOrganizationMgtPluginException {
if (node == null || node.getDeviceId() <= 0 || maxDepth <= 0) {
if (deviceId <= 0 || maxDepth <= 0) {
String msg = "Invalid input parameters for retrieving parent devices. Params : " +
"deviceID = " + (node != null ? node.getDeviceId() : null) + ", maxDepth = " + maxDepth +
"deviceID = " + deviceId + ", maxDepth = " + maxDepth +
", includeDevice = " + includeDevice;
throw new BadRequestException(msg);
}
try {
// Open a database connection
ConnectionManagerUtil.openDBConnection();
return deviceOrganizationDao.getParentsOfDeviceNode(node, maxDepth, includeDevice);
return deviceOrganizationDao.getParentsOfDeviceNode(deviceId, maxDepth, includeDevice);
} catch (DBConnectionException e) {
String msg = "Error occurred while obtaining the database connection to retrieve parent devices for : " +
"device ID = " + node.getDeviceId() + ", maxDepth = " + maxDepth + ", includeDevice = " +
"device ID = " + deviceId + ", maxDepth = " + maxDepth + ", includeDevice = " +
includeDevice;
log.error(msg);
throw new DeviceOrganizationMgtPluginException(msg, e);
} catch (DeviceOrganizationMgtDAOException e) {
String msg = "Error occurred in the database level while retrieving parent devices for : " +
"device ID = " + node.getDeviceId() + ", maxDepth = " + maxDepth + ", includeDevice = " +
"device ID = " + deviceId + ", maxDepth = " + maxDepth + ", includeDevice = " +
includeDevice;
log.error(msg);
throw new DeviceOrganizationMgtPluginException(msg, e);

@ -17,7 +17,7 @@
*/
package io.entgra.device.mgt.core.device.mgt.extensions.device.organization.spi;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceNode;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceNodeResult;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceOrganization;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.PaginationRequest;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.exception.DeviceOrganizationMgtPluginException;
@ -42,25 +42,25 @@ public interface DeviceOrganizationService {
/**
* Retrieves a list of child nodes of a given device node, up to a specified depth.
*
* @param node The parent device node.
* @param deviceID The parent device ID.
* @param maxDepth The maximum depth of child nodes to retrieve.
* @param includeDevice Indicates whether to include device information in the retrieved nodes.
* @return A list of child device nodes.
* @throws DeviceOrganizationMgtPluginException If an error occurs during the operation.
*/
List<DeviceNode> getChildrenOfDeviceNode(DeviceNode node, int maxDepth, boolean includeDevice)
DeviceNodeResult getChildrenOfDeviceNode(int deviceID, int maxDepth, boolean includeDevice)
throws DeviceOrganizationMgtPluginException;
/**
* Retrieves a list of parent nodes of a given device node, up to a specified depth.
*
* @param node The child device node.
* @param deviceID The child device ID.
* @param maxDepth The maximum depth of parent nodes to retrieve.
* @param includeDevice Indicates whether to include device information in the retrieved nodes.
* @return A list of parent device nodes.
* @throws DeviceOrganizationMgtPluginException If an error occurs during the operation.
*/
List<DeviceNode> getParentsOfDeviceNode(DeviceNode node, int maxDepth, boolean includeDevice)
DeviceNodeResult getParentsOfDeviceNode(int deviceID, int maxDepth, boolean includeDevice)
throws DeviceOrganizationMgtPluginException;
/**
@ -73,6 +73,7 @@ public interface DeviceOrganizationService {
/**
* Retrieves device Organization Leafs
*
* @return A list of leaf device organizations.
* @throws DeviceOrganizationMgtPluginException
*/
@ -80,6 +81,7 @@ public interface DeviceOrganizationService {
/**
* Retrieves device Organization Roots
*
* @return A list of root device organizations.
* @throws DeviceOrganizationMgtPluginException
*/

@ -3,7 +3,7 @@ package io.entgra.device.mgt.core.device.mgt.extensions.device.organization;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dao.DeviceOrganizationDAO;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dao.DeviceOrganizationDAOFactory;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dao.util.ConnectionManagerUtil;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceNode;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceNodeResult;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceOrganization;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.exception.DBConnectionException;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.exception.DeviceOrganizationMgtDAOException;
@ -32,26 +32,22 @@ public class DAOTest extends BaseDeviceOrganizationTest {
@Test(dependsOnMethods = "testAddDeviceOrganizationDAO")
public void testGetChildrenOfDAO() throws DBConnectionException, DeviceOrganizationMgtDAOException {
ConnectionManagerUtil.openDBConnection();
DeviceNode node = new DeviceNode();
node.setDeviceId(2);
int deviceId = 2;
int maxDepth = 4;
boolean includeDevice = true;
List<DeviceNode> childrenList = deviceOrganizationDAO.getChildrenOfDeviceNode(node, maxDepth, includeDevice);
DeviceNodeResult childrenList = deviceOrganizationDAO.getChildrenOfDeviceNode(deviceId, maxDepth, includeDevice);
ConnectionManagerUtil.closeDBConnection();
log.info(childrenList.size());
Assert.assertNotNull(childrenList, "Cannot be null");
}
@Test(dependsOnMethods = "testAddDeviceOrganizationDAO")
public void testGetParentsOfDAO() throws DBConnectionException, DeviceOrganizationMgtDAOException {
ConnectionManagerUtil.openDBConnection();
DeviceNode node = new DeviceNode();
node.setDeviceId(4);
int deviceID = 4;
int maxDepth = 4;
boolean includeDevice = false;
List<DeviceNode> parentList = deviceOrganizationDAO.getParentsOfDeviceNode(node, maxDepth, includeDevice);
DeviceNodeResult parentList = deviceOrganizationDAO.getParentsOfDeviceNode(deviceID, maxDepth, includeDevice);
ConnectionManagerUtil.closeDBConnection();
log.info(parentList.size());
Assert.assertNotNull(parentList, "Cannot be null");
}

@ -1,6 +1,5 @@
package io.entgra.device.mgt.core.device.mgt.extensions.device.organization;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceNode;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceOrganization;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.exception.BadRequestException;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.exception.DeviceOrganizationMgtPluginException;
@ -25,50 +24,50 @@ public class ServiceNegativeTest extends BaseDeviceOrganizationTest {
log.info("Service test initialized");
}
@Test(description = "This method tests Get Children Of method under negative circumstances with null data",
@Test(description = "This method tests Get Children Of method under negative circumstances with negative deviceId",
expectedExceptions = {DeviceOrganizationMgtPluginException.class})
public void testGetChildrenOfWithInvalidInput() throws DeviceOrganizationMgtPluginException {
DeviceNode invalidNode = null;
int deviceId = -1;
int maxDepth = -1;
boolean includeDevice = true;
deviceOrganizationService.getChildrenOfDeviceNode(invalidNode, maxDepth, includeDevice);
deviceOrganizationService.getChildrenOfDeviceNode(deviceId, maxDepth, includeDevice);
}
@Test(description = "This method tests Get Children Of method under negative circumstances with an invalid DeviceNode",
@Test(description = "This method tests Get Children Of method under negative circumstances with an invalid deviceId",
expectedExceptions = {DeviceOrganizationMgtPluginException.class})
public void testGetChildrenOfWithInvalidDeviceNode() throws DeviceOrganizationMgtPluginException {
DeviceNode invalidNode = new DeviceNode(); // Provide an invalid DeviceNode
int deviceId = 0;
int maxDepth = 2;
boolean includeDevice = true;
deviceOrganizationService.getChildrenOfDeviceNode(invalidNode, maxDepth, includeDevice);
deviceOrganizationService.getChildrenOfDeviceNode(deviceId, maxDepth, includeDevice);
}
@Test(description = "This method tests Get Parents Of method under negative circumstances with null data",
@Test(description = "This method tests Get Parents Of method under negative circumstances with invalid data",
expectedExceptions = {DeviceOrganizationMgtPluginException.class})
public void testGetParentsOfWithInvalidInput() throws DeviceOrganizationMgtPluginException {
DeviceNode invalidNode = null;
int deviceID = 0;
int maxDepth = -1;
boolean includeDevice = true;
deviceOrganizationService.getParentsOfDeviceNode(invalidNode, maxDepth, includeDevice);
deviceOrganizationService.getParentsOfDeviceNode(deviceID, maxDepth, includeDevice);
}
@Test(description = "This method tests Get Parents Of method under negative circumstances with an invalid DeviceNode"
@Test(description = "This method tests Get Parents Of method under negative circumstances with an invalid ID"
, expectedExceptions = {DeviceOrganizationMgtPluginException.class})
public void testGetParentsOfWithInvalidDeviceNode() throws DeviceOrganizationMgtPluginException {
DeviceNode invalidNode = new DeviceNode(); // Provide an invalid DeviceNode
int deviceID = -2;
int maxDepth = 2;
boolean includeDevice = true;
deviceOrganizationService.getParentsOfDeviceNode(invalidNode, maxDepth, includeDevice);
deviceOrganizationService.getParentsOfDeviceNode(deviceID, maxDepth, includeDevice);
}
@Test(description = "This method tests Get Parents Of method under negative circumstances with an invalid DeviceNode"
, expectedExceptions = {DeviceOrganizationMgtPluginException.class}
)
public void testGetParentsOfWithNullDeviceNode() throws DeviceOrganizationMgtPluginException {
DeviceNode invalidNode = null; // Provide an invalid DeviceNode
int deviceID = -1;
int maxDepth = 2;
boolean includeDevice = true;
deviceOrganizationService.getParentsOfDeviceNode(invalidNode, maxDepth, includeDevice);
deviceOrganizationService.getParentsOfDeviceNode(deviceID, maxDepth, includeDevice);
}

@ -1,6 +1,6 @@
package io.entgra.device.mgt.core.device.mgt.extensions.device.organization;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceNode;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceNodeResult;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.DeviceOrganization;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.dto.PaginationRequest;
import io.entgra.device.mgt.core.device.mgt.extensions.device.organization.exception.DeviceOrganizationMgtPluginException;
@ -31,12 +31,10 @@ public class ServiceTest extends BaseDeviceOrganizationTest {
public void testGetChildrenOf() throws DeviceOrganizationMgtPluginException {
boolean exists = deviceOrganizationService.isDeviceIdExist(17);
if (exists) {
DeviceNode deviceNode = new DeviceNode();
deviceNode.setDeviceId(17);
int deviceID = 17;
int maxDepth = 10;
boolean includeDevice = true;
List<DeviceNode> childrenList = deviceOrganizationService.getChildrenOfDeviceNode(deviceNode, maxDepth, includeDevice);
boolean includeDevice = false;
DeviceNodeResult childrenList = deviceOrganizationService.getChildrenOfDeviceNode(deviceID, maxDepth, includeDevice);
Assert.assertNotNull(childrenList, "Cannot be null");
}
}
@ -45,11 +43,10 @@ public class ServiceTest extends BaseDeviceOrganizationTest {
public void testGetParentsOf() throws DeviceOrganizationMgtPluginException {
boolean exists = deviceOrganizationService.isChildDeviceIdExist(20);
if (exists) {
DeviceNode deviceNode = new DeviceNode();
deviceNode.setDeviceId(20);
int deviceID = 20;
int maxDepth = 3;
boolean includeDevice = false;
List<DeviceNode> parentList = deviceOrganizationService.getParentsOfDeviceNode(deviceNode, maxDepth, includeDevice);
DeviceNodeResult parentList = deviceOrganizationService.getParentsOfDeviceNode(deviceID, maxDepth, includeDevice);
Assert.assertNotNull(parentList, "Cannot be null");
}
@ -246,7 +243,7 @@ public class ServiceTest extends BaseDeviceOrganizationTest {
@Test(dependsOnMethods = "testAddDeviceOrganizationWithNullParent")
public void testGetRootOrganizations() throws DeviceOrganizationMgtPluginException {
int offset = 0;
int offset = 0;
int limit = 10;
PaginationRequest request = new PaginationRequest(offset, limit);
List<DeviceOrganization> organizations = deviceOrganizationService.getDeviceOrganizationRoots(request);
@ -263,7 +260,7 @@ public class ServiceTest extends BaseDeviceOrganizationTest {
@Test(dependsOnMethods = "testAddDeviceOrganizationWithNullParent")
public void testGetLeafOrganizationsWithNullParents() throws DeviceOrganizationMgtPluginException {
int offset = 0;
int offset = 0;
int limit = 10;
PaginationRequest request = new PaginationRequest(offset, limit);
List<DeviceOrganization> organizations = deviceOrganizationService.getDeviceOrganizationLeafs(request);
@ -279,7 +276,7 @@ public class ServiceTest extends BaseDeviceOrganizationTest {
@Test(dependsOnMethods = "testAddDeviceOrganization")
public void testGetLeafOrganizations() throws DeviceOrganizationMgtPluginException {
int offset = 0;
int offset = 0;
int limit = 10;
PaginationRequest request = new PaginationRequest(offset, limit);
List<DeviceOrganization> organizations = deviceOrganizationService.getDeviceOrganizationLeafs(request);

@ -856,12 +856,12 @@ CREATE TABLE IF NOT EXISTS DM_DEVICE_SUB_TYPE
-- DM_DEVICE_ORGANIZATION TABLE--
CREATE TABLE IF NOT EXISTS DM_DEVICE_ORGANIZATION
(
ID INT NOT NULL AUTO_INCREMENT,
ORGANIZATION_ID INT NOT NULL AUTO_INCREMENT,
DEVICE_ID INT(11) NOT NULL,
PARENT_DEVICE_ID INT(11) DEFAULT NULL,
DEVICE_ORGANIZATION_META TEXT DEFAULT NULL,
LAST_UPDATED_TIMESTAMP TIMESTAMP NOT NULL,
PRIMARY KEY (ID),
PRIMARY KEY (ORGANIZATION_ID),
CONSTRAINT fk_DM_DEVICE_DM_ID FOREIGN KEY (DEVICE_ID)
REFERENCES DM_DEVICE (ID) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT fk_DM_PARENT_DEVICE_DM_ID FOREIGN KEY (PARENT_DEVICE_ID)

@ -20,12 +20,12 @@
-- DM_DEVICE_ORGANIZATION TABLE--
CREATE TABLE IF NOT EXISTS DM_DEVICE_ORGANIZATION (
ID INT NOT NULL AUTO_INCREMENT,
ORGANIZATION_ID INT NOT NULL AUTO_INCREMENT,
DEVICE_ID INT(11) NOT NULL,
PARENT_DEVICE_ID INT(11) DEFAULT NULL,
DEVICE_ORGANIZATION_META TEXT DEFAULT NULL,
LAST_UPDATED_TIMESTAMP TIMESTAMP NOT NULL,
PRIMARY KEY (ID),
PRIMARY KEY (ORGANIZATION_ID),
CONSTRAINT fk_DM_DEVICE_DM_ID FOREIGN KEY (DEVICE_ID)
REFERENCES DM_DEVICE (ID) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT fk_DM_PARENT_DEVICE_DM_ID FOREIGN KEY (PARENT_DEVICE_ID)

@ -843,12 +843,12 @@ CREATE TABLE SUB_OPERATION_TEMPLATE (
-- DM_DEVICE_ORGANIZATION TABLE--
CREATE TABLE IF NOT EXISTS DM_DEVICE_ORGANIZATION
(
ID INT NOT NULL AUTO_INCREMENT,
ORGANIZATION_ID INT NOT NULL AUTO_INCREMENT,
DEVICE_ID INT(11) NOT NULL,
PARENT_DEVICE_ID INT(11) DEFAULT NULL,
DEVICE_ORGANIZATION_META TEXT DEFAULT NULL,
LAST_UPDATED_TIMESTAMP TIMESTAMP NOT NULL,
PRIMARY KEY (ID),
PRIMARY KEY (ORGANIZATION_ID),
CONSTRAINT fk_DM_DEVICE_DM_ID FOREIGN KEY (DEVICE_ID)
REFERENCES DM_DEVICE (ID) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT fk_DM_PARENT_DEVICE_DM_ID FOREIGN KEY (PARENT_DEVICE_ID)

@ -917,12 +917,12 @@ CREATE TABLE SUB_OPERATION_TEMPLATE (
-- DM_DEVICE_ORGANIZATION TABLE--
CREATE TABLE IF NOT EXISTS DM_DEVICE_ORGANIZATION (
ID INT NOT NULL IDENTITY(1,1),
ORGANIZATION_ID INT NOT NULL IDENTITY(1,1),
DEVICE_ID INT NOT NULL,
PARENT_DEVICE_ID INT DEFAULT NULL,
DEVICE_ORGANIZATION_META TEXT DEFAULT NULL,
LAST_UPDATED_TIMESTAMP BIGINT NOT NULL,
PRIMARY KEY (ID),
PRIMARY KEY (ORGANIZATION_ID),
CONSTRAINT fk_DM_DEVICE_DM_ID FOREIGN KEY (DEVICE_ID)
REFERENCES DM_DEVICE (ID) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT fk_DM_PARENT_DEVICE_DM_ID FOREIGN KEY (PARENT_DEVICE_ID)

@ -910,12 +910,12 @@ CREATE TABLE SUB_OPERATION_TEMPLATE (
-- DM_DEVICE_ORGANIZATION TABLE--
CREATE TABLE IF NOT EXISTS DM_DEVICE_ORGANIZATION (
ID INT NOT NULL AUTO_INCREMENT,
ORGANIZATION_ID INT NOT NULL AUTO_INCREMENT,
DEVICE_ID INT(11) NOT NULL,
PARENT_DEVICE_ID INT(11) DEFAULT NULL,
DEVICE_ORGANIZATION_META TEXT DEFAULT NULL,
LAST_UPDATED_TIMESTAMP TIMESTAMP NOT NULL,
PRIMARY KEY (ID),
PRIMARY KEY (ORGANIZATION_ID),
CONSTRAINT fk_DM_DEVICE_DM_ID FOREIGN KEY (DEVICE_ID)
REFERENCES DM_DEVICE (ID) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT fk_DM_PARENT_DEVICE_DM_ID FOREIGN KEY (PARENT_DEVICE_ID)

@ -1197,7 +1197,7 @@ CREATE SEQUENCE SUB_OPERATION_TEMPLATE_seq START WITH 1 INCREMENT BY 1;
-- DM_DEVICE_ORGANIZATION TABLE--
CREATE TABLE IF NOT EXISTS DM_DEVICE_ORGANIZATION (
ID NUMBER(10) NOT NULL,
ORGANIZATION_ID NUMBER(10) NOT NULL,
DEVICE_ID NUMBER(10) NOT NULL,
PARENT_DEVICE_ID NUMBER(10) DEFAULT NULL,
DEVICE_ORGANIZATION_META CLOB DEFAULT NULL,
@ -1219,10 +1219,10 @@ BEFORE INSERT
ON DM_DEVICE_ORGANIZATION
REFERENCING NEW AS NEW
FOR EACH ROW
WHEN (NEW.ID IS NULL)
WHEN (NEW.ORGANIZATION_ID IS NULL)
BEGIN
SELECT DM_DEVICE_ORGANIZATION_seq.NEXTVAL
INTO :NEW.ID
INTO :NEW.ORGANIZATION_ID
FROM DUAL;
END;
/

@ -850,12 +850,12 @@ CREATE TABLE DM_DEVICE_CERTIFICATE (
CREATE SEQUENCE DM_DEVICE_ORGANIZATION_seq;
CREATE TABLE IF NOT EXISTS DM_DEVICE_ORGANIZATION (
ID INTEGER DEFAULT NEXTVAL ('DM_DEVICE_ORGANIZATION_seq') NOT NULL,
ORGANIZATION_ID INTEGER DEFAULT NEXTVAL ('DM_DEVICE_ORGANIZATION_seq') NOT NULL,
DEVICE_ID INTEGER NOT NULL,
PARENT_DEVICE_ID INTEGER DEFAULT NULL,
DEVICE_ORGANIZATION_META TEXT DEFAULT NULL,
LAST_UPDATED_TIMESTAMP TIMESTAMP(0) NOT NULL NOT NULL,
PRIMARY KEY (ID),
PRIMARY KEY (ORGANIZATION_ID),
CONSTRAINT fk_DM_DEVICE_DM_ID FOREIGN KEY (DEVICE_ID)
REFERENCES DM_DEVICE (ID) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT fk_DM_PARENT_DEVICE_DM_ID FOREIGN KEY (PARENT_DEVICE_ID)

Loading…
Cancel
Save