Implement Device Tagging Feature #492

Merged
tcdlpds merged 5 commits from Gimhan-minion/device-mgt-core:master into master 3 months ago
There is no content yet.
Gimhan-minion added 2 commits 3 months ago
Gimhan-minion changed title from WIP: Implement Device Tagging Feature to Implement Device Tagging Feature 3 months ago
Sasini_Sandamali reviewed 3 months ago
produces = MediaType.APPLICATION_JSON,
httpMethod = "GET",
value = "Getting the List of Tags",
notes = "",

For this notes attribute, better to add an explanation about what the specific API endpoint does

For this notes attribute, better to add an explanation about what the specific API endpoint does
Gimhan-minion marked this conversation as resolved
Sasini_Sandamali reviewed 3 months ago
@Override
public Response updateTag(@QueryParam("tagId") Integer tagId, @QueryParam("tagName") String tagName, TagInfo tagInfo) {
RequestValidationUtil.validateTagDetails(tagId, tagName, tagInfo);
try {

Don't we validate tagId,tagName and tagInfo here?

Don't we validate tagId,tagName and tagInfo here?

Here the update is possible by providing either the tagId or tagName. Therefore, for rechecking which type of parameter is provided we need to have a seperate null check here.

Here the update is possible by providing either the tagId or tagName. Therefore, for rechecking which type of parameter is provided we need to have a seperate null check here.
Gimhan-minion marked this conversation as resolved
tcdlpds requested changes 3 months ago
return Response.status(Response.Status.OK).entity(devices).build();
}
if (tags != null && !tags.isEmpty()) {
Owner

Can you explain the logic that you are going to achieve here?

Can you explain the logic that you are going to achieve here?
Gimhan-minion marked this conversation as resolved
tag = DeviceMgtAPIUtils.getTagManagementService().getTagByName(tagName);
}
if (tag == null) {
String msg = "Tag not found.";
Owner

Improve the message as you have done in the catch block.

Improve the message as you have done in the catch block.
Gimhan-minion marked this conversation as resolved
public Response addDeviceTagMapping(TagMappingInfo tagMappingInfo) {
RequestValidationUtil.validateTagMappingDetails(tagMappingInfo);
try {
TagMappingDTO tagMappingDTO = new TagMappingDTO(tagMappingInfo.getDeviceIdentifiers(),
Owner

Better to move this part into service method.

Better to move this part into service method.
Gimhan-minion marked this conversation as resolved
}
}
public static void validateTagDetails(Integer tagId, String tagName, TagInfo tagInfo) {
Owner

Why didn't we use StringUtils to validate the tag name?

Why didn't we use StringUtils to validate the tag name?
Gimhan-minion marked this conversation as resolved
}
public static void validateTagDetails(Integer tagId, String tagName, TagInfo tagInfo) {
if (tagId == null && tagName == null) {
Owner

Shouldn't we handle negatives and 0?

Shouldn't we handle negatives and 0?
Gimhan-minion marked this conversation as resolved
new ErrorResponse.ErrorResponseBuilder().setCode(400l).setMessage(msg).build());
}
if (tagInfo == null) {
throw new InputValidationException(
Owner

Log the error and throw.

Log the error and throw.
Gimhan-minion marked this conversation as resolved
public static void validateTagListDetails(List<TagInfo> tagInfo) {
if (tagInfo == null) {
throw new InputValidationException(
Owner

Log the error and throw.

Log the error and throw.
Gimhan-minion marked this conversation as resolved
}
}
public static void validateTagMappingDetails(TagMappingInfo tagMappingInfo) {
Owner

Add Java Doc comment, please check all the places and add.

Add Java Doc comment, please check all the places and add.
Gimhan-minion marked this conversation as resolved
public static void validateTagMappingDetails(TagMappingInfo tagMappingInfo) {
if (tagMappingInfo == null) {
throw new InputValidationException(
Owner

Log the error and throw.

Log the error and throw.
Gimhan-minion marked this conversation as resolved
new ErrorResponse.ErrorResponseBuilder().setCode(400L).setMessage("Request body is empty").build());
} else if (tagMappingInfo.getDeviceIdentifiers() == null || tagMappingInfo.getDeviceType() == null
|| tagMappingInfo.getTags() == null) {
throw new InputValidationException(
Owner

Log the error and throw.

Log the error and throw.
Gimhan-minion marked this conversation as resolved
try {
connection = getConnection();
preparedStatement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
Owner

Use try with resources.

Use try with resources.
Gimhan-minion marked this conversation as resolved
int[] updateCounts = preparedStatement.executeBatch();
for (int count : updateCounts) {
if (count == PreparedStatement.EXECUTE_FAILED) {
throw new TagManagementDAOException("Error occurred while adding tags, adding some tags failed.");
Owner

Log and throw the error

Log and throw the error
Gimhan-minion marked this conversation as resolved
import io.entgra.device.mgt.core.device.mgt.common.exceptions.ConflictException;
import io.entgra.device.mgt.core.device.mgt.common.metadata.mgt.DeviceStatusManagementService;
import io.entgra.device.mgt.core.device.mgt.core.dao.TenantDAO;
import io.entgra.device.mgt.core.device.mgt.core.dao.*;
Owner

Don't use wild card imports

Don't use wild card imports
Gimhan-minion marked this conversation as resolved
}
try {
int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId();
DeviceManagementDAOFactory.beginTransaction();
Owner

In the try block this should be the first line of a transaction handling logic.

In the try block this should be the first line of a transaction handling logic.
Gimhan-minion marked this conversation as resolved
}
try {
int tenantId = CarbonContext.getThreadLocalCarbonContext().getTenantId();
DeviceManagementDAOFactory.openConnection();
Owner

Don't we need to start a transaction here?

Don't we need to start a transaction here?
Gimhan-minion marked this conversation as resolved
thameera requested changes 3 months ago
import io.entgra.device.mgt.core.device.mgt.common.tag.mgt.Tag;
import io.entgra.device.mgt.core.device.mgt.core.dao.*;
import java.sql.*;
Collaborator

Importing specific classes individually is better.

Importing specific classes individually is better.
Gimhan-minion marked this conversation as resolved
import io.entgra.device.mgt.core.device.mgt.common.metadata.mgt.DeviceStatusManagementService;
import io.entgra.device.mgt.core.device.mgt.core.authorization.GroupAccessAuthorizationServiceImpl;
import io.entgra.device.mgt.core.device.mgt.core.metadata.mgt.DeviceStatusManagementServiceImpl;
import io.entgra.device.mgt.core.device.mgt.core.service.*;
Collaborator

Import specific classes individually

Import specific classes individually
Gimhan-minion marked this conversation as resolved
import io.entgra.device.mgt.core.device.mgt.common.exceptions.ConflictException;
import io.entgra.device.mgt.core.device.mgt.common.metadata.mgt.DeviceStatusManagementService;
import io.entgra.device.mgt.core.device.mgt.core.dao.TenantDAO;
import io.entgra.device.mgt.core.device.mgt.core.dao.*;
Collaborator

Import specific classes individually

Import specific classes individually
Gimhan-minion marked this conversation as resolved
charithag approved these changes 3 months ago
charithag left a comment
Owner

This implementation does not have a way to retrieve a device with all tags it has. It only provide assigned tags along with a device, if filtered by tags.

This implementation does not have a way to retrieve a device with all tags it has. It only provide assigned tags along with a device, if filtered by tags.
import io.entgra.device.mgt.core.device.mgt.api.jaxrs.service.impl.util.RequestValidationUtil;
import io.entgra.device.mgt.core.device.mgt.api.jaxrs.util.DeviceMgtAPIUtils;
import io.entgra.device.mgt.core.device.mgt.common.exceptions.BadRequestException;
import io.entgra.device.mgt.core.device.mgt.common.tag.mgt.*;
Owner

Remove * imports

Remove * imports
Gimhan-minion marked this conversation as resolved
} catch (TagManagementException e) {
String msg = "Error occurred while getting tags.";
log.error(msg, e);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
Owner

As this API produces APPLICATION_JSON as result, entity should be an object which can parse to a valid json. Correct this in all other applicable places as well.

As this API produces APPLICATION_JSON as result, entity should be an object which can parse to a valid json. Correct this in all other applicable places as well.
Gimhan-minion marked this conversation as resolved
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(msg).build();
} catch (BadRequestException e) {
String msg = "Error occurred while adding tags. Please check the request" ;
log.error(msg, e);
Owner

No need to log, bad requests as an error. This is because, bad requests are made by clients and thus client needs to correct it from that end. It is sufficient to log validation failures as debug logs. Applicable for other places as well.

No need to log, bad requests as an error. This is because, bad requests are made by clients and thus client needs to correct it from that end. It is sufficient to log validation failures as debug logs. Applicable for other places as well.
Gimhan-minion marked this conversation as resolved
public static TagManagementProviderService getTagManagementService() {
TagManagementProviderService tagManagementService;
PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext();
tagManagementService =
Owner

This will retrieve TagManagementProviderService from OSGi registry every time the method getTagManagementService() is invoked. Since TagManagementProviderService instance is not changing over the server runtime, it is good to lazy load this and assign to a static variable for future use.

This will retrieve TagManagementProviderService from OSGi registry every time the method getTagManagementService() is invoked. Since TagManagementProviderService instance is not changing over the server runtime, it is good to lazy load this and assign to a static variable for future use.
Gimhan-minion marked this conversation as resolved
charithag requested changes 3 months ago
charithag left a comment
Owner

This implementation does not have a way to retrieve a device with all tags it has. It only provide assigned tags along with a device, if filtered by tags.

This implementation does not have a way to retrieve a device with all tags it has. It only provide assigned tags along with a device, if filtered by tags.
charithag requested changes 3 months ago
charithag left a comment
Owner

This implementation does not have a way to retrieve a device with all tags it has. It only provide assigned tags along with a device, if filtered by tags.

This implementation does not have a way to retrieve a device with all tags it has. It only provide assigned tags along with a device, if filtered by tags.

This implementation does not have a way to retrieve a device with all tags it has. It only provide assigned tags along with a device, if filtered by tags.

I'm assuming what mentioned above is that if we do not provide tags as a query parameter in the search filter, it will not provide the tags.

In the implementation, if you call the getDevices method without any params, it will return the list of all devices with a payload like this.

https://{{server-ip}}:{{port}}/api/device-mgt/v1.0/devices

{
    "devices": [
        {
            "id": 1,
            "name": "device1",
            "type": "power-meter",
            "deviceIdentifier": "meter-1",
            "lastUpdatedTimeStamp": 1723800132280,
            "enrolmentInfo": {
                "id": 7,
                "isTransferred": false,
                "dateOfEnrolment": 1723800317986,
                "dateOfLastUpdate": 1723800317986,
                "ownership": "BYOD",
                "status": "ACTIVE",
                "owner": "admin",
                "tags": [
                    "checkinggggg",
                    "new",
                    "tagss"
                ]
            },
            "cost": 0.0,
            "daysUsed": 0,
            "deviceStatusInfo": []
        },
        {
            "id": 2,
            "name": "device2",
            "type": "power-meter",
            "deviceIdentifier": "meter-2",
            "lastUpdatedTimeStamp": 1723800133281,
            "enrolmentInfo": {
                "id": 8,
                "isTransferred": false,
                "dateOfEnrolment": 1723800317986,
                "dateOfLastUpdate": 1723800317986,
                "ownership": "BYOD",
                "status": "ACTIVE",
                "owner": "admin",
                "tags": []
            },
            "cost": 0.0,
            "daysUsed": 0,
            "deviceStatusInfo": []
        },
        {
            "id": 3,
            "name": "device3",
            "type": "power-meter",
            "deviceIdentifier": "meter-3",
            "lastUpdatedTimeStamp": 1723800134281,
            "enrolmentInfo": {
                "id": 9,
                "isTransferred": false,
                "dateOfEnrolment": 1723800317986,
                "dateOfLastUpdate": 1723800317986,
                "ownership": "BYOD",
                "status": "ACTIVE",
                "owner": "admin",
                "tags": []
            },
            "cost": 0.0,
            "daysUsed": 0,
            "deviceStatusInfo": []
        },
        {
            "id": 4,
            "name": "device4",
            "type": "power-meter",
            "deviceIdentifier": "meter-4",
            "lastUpdatedTimeStamp": 1723800135283,
            "enrolmentInfo": {
                "id": 10,
                "isTransferred": false,
                "dateOfEnrolment": 1723800317986,
                "dateOfLastUpdate": 1723800317986,
                "ownership": "BYOD",
                "status": "ACTIVE",
                "owner": "admin",
                "tags": [
                    "checkinggggg",
                    "sds",
                    "tagss"
                ]
            },
            "cost": 0.0,
            "daysUsed": 0,
            "deviceStatusInfo": []
        },
        {
            "id": 5,
            "name": "device5",
            "type": "power-meter",
            "deviceIdentifier": "meter-5",
            "lastUpdatedTimeStamp": 1723800136283,
            "enrolmentInfo": {
                "id": 11,
                "isTransferred": false,
                "dateOfEnrolment": 1723800317986,
                "dateOfLastUpdate": 1723800317986,
                "ownership": "BYOD",
                "status": "ACTIVE",
                "owner": "admin",
                "tags": []
            },
            "cost": 0.0,
            "daysUsed": 0,
            "deviceStatusInfo": []
        }
    ],
    "totalCost": 0.0,
    "deviceCount": 0.0,
    "count": 5
}

Scenario 2
If we provide a tag like 'tagss' in the query parameter, then it will filter by those.

https://{{server-ip}}:{{port}}/api/device-mgt/v1.0/devices?tags=tagss


{
    "devices": [
        {
            "id": 1,
            "name": "device1",
            "type": "power-meter",
            "deviceIdentifier": "meter-1",
            "lastUpdatedTimeStamp": 1723800132280,
            "enrolmentInfo": {
                "id": 7,
                "isTransferred": false,
                "dateOfEnrolment": 1723800317986,
                "dateOfLastUpdate": 1723800317986,
                "ownership": "BYOD",
                "status": "ACTIVE",
                "owner": "admin",
                "tags": [
                    "checkinggggg",
                    "new",
                    "tagss"
                ]
            },
            "cost": 0.0,
            "daysUsed": 0,
            "deviceStatusInfo": []
        },
        {
            "id": 4,
            "name": "device4",
            "type": "power-meter",
            "deviceIdentifier": "meter-4",
            "lastUpdatedTimeStamp": 1723800135283,
            "enrolmentInfo": {
                "id": 10,
                "isTransferred": false,
                "dateOfEnrolment": 1723800317986,
                "dateOfLastUpdate": 1723800317986,
                "ownership": "BYOD",
                "status": "ACTIVE",
                "owner": "admin",
                "tags": [
                    "checkinggggg",
                    "sds",
                    "tagss"
                ]
            },
            "cost": 0.0,
            "daysUsed": 0,
            "deviceStatusInfo": []
        }
    ],
    "totalCost": 0.0,
    "deviceCount": 0.0,
    "count": 2
}

So according to the requirement mentioned above, as I understand it, even if we provide a filter or not, it returns the attached tags within the payload.

Hoping i got this correct :)

PS: All the other requested changes except above are updated

> This implementation does not have a way to retrieve a device with all tags it has. It only provide assigned tags along with a device, if filtered by tags. I'm assuming what mentioned above is that if we do not provide tags as a query parameter in the search filter, it will not provide the tags. In the implementation, if you call the getDevices method without any params, it will return the list of all devices with a payload like this. ``` https://{{server-ip}}:{{port}}/api/device-mgt/v1.0/devices { "devices": [ { "id": 1, "name": "device1", "type": "power-meter", "deviceIdentifier": "meter-1", "lastUpdatedTimeStamp": 1723800132280, "enrolmentInfo": { "id": 7, "isTransferred": false, "dateOfEnrolment": 1723800317986, "dateOfLastUpdate": 1723800317986, "ownership": "BYOD", "status": "ACTIVE", "owner": "admin", "tags": [ "checkinggggg", "new", "tagss" ] }, "cost": 0.0, "daysUsed": 0, "deviceStatusInfo": [] }, { "id": 2, "name": "device2", "type": "power-meter", "deviceIdentifier": "meter-2", "lastUpdatedTimeStamp": 1723800133281, "enrolmentInfo": { "id": 8, "isTransferred": false, "dateOfEnrolment": 1723800317986, "dateOfLastUpdate": 1723800317986, "ownership": "BYOD", "status": "ACTIVE", "owner": "admin", "tags": [] }, "cost": 0.0, "daysUsed": 0, "deviceStatusInfo": [] }, { "id": 3, "name": "device3", "type": "power-meter", "deviceIdentifier": "meter-3", "lastUpdatedTimeStamp": 1723800134281, "enrolmentInfo": { "id": 9, "isTransferred": false, "dateOfEnrolment": 1723800317986, "dateOfLastUpdate": 1723800317986, "ownership": "BYOD", "status": "ACTIVE", "owner": "admin", "tags": [] }, "cost": 0.0, "daysUsed": 0, "deviceStatusInfo": [] }, { "id": 4, "name": "device4", "type": "power-meter", "deviceIdentifier": "meter-4", "lastUpdatedTimeStamp": 1723800135283, "enrolmentInfo": { "id": 10, "isTransferred": false, "dateOfEnrolment": 1723800317986, "dateOfLastUpdate": 1723800317986, "ownership": "BYOD", "status": "ACTIVE", "owner": "admin", "tags": [ "checkinggggg", "sds", "tagss" ] }, "cost": 0.0, "daysUsed": 0, "deviceStatusInfo": [] }, { "id": 5, "name": "device5", "type": "power-meter", "deviceIdentifier": "meter-5", "lastUpdatedTimeStamp": 1723800136283, "enrolmentInfo": { "id": 11, "isTransferred": false, "dateOfEnrolment": 1723800317986, "dateOfLastUpdate": 1723800317986, "ownership": "BYOD", "status": "ACTIVE", "owner": "admin", "tags": [] }, "cost": 0.0, "daysUsed": 0, "deviceStatusInfo": [] } ], "totalCost": 0.0, "deviceCount": 0.0, "count": 5 } ``` Scenario 2 If we provide a tag like 'tagss' in the query parameter, then it will filter by those. ``` https://{{server-ip}}:{{port}}/api/device-mgt/v1.0/devices?tags=tagss { "devices": [ { "id": 1, "name": "device1", "type": "power-meter", "deviceIdentifier": "meter-1", "lastUpdatedTimeStamp": 1723800132280, "enrolmentInfo": { "id": 7, "isTransferred": false, "dateOfEnrolment": 1723800317986, "dateOfLastUpdate": 1723800317986, "ownership": "BYOD", "status": "ACTIVE", "owner": "admin", "tags": [ "checkinggggg", "new", "tagss" ] }, "cost": 0.0, "daysUsed": 0, "deviceStatusInfo": [] }, { "id": 4, "name": "device4", "type": "power-meter", "deviceIdentifier": "meter-4", "lastUpdatedTimeStamp": 1723800135283, "enrolmentInfo": { "id": 10, "isTransferred": false, "dateOfEnrolment": 1723800317986, "dateOfLastUpdate": 1723800317986, "ownership": "BYOD", "status": "ACTIVE", "owner": "admin", "tags": [ "checkinggggg", "sds", "tagss" ] }, "cost": 0.0, "daysUsed": 0, "deviceStatusInfo": [] } ], "totalCost": 0.0, "deviceCount": 0.0, "count": 2 } ``` So according to the requirement mentioned above, as I understand it, even if we provide a filter or not, it returns the attached tags within the payload. Hoping i got this correct :) PS: All the other requested changes except above are updated
Gimhan-minion force-pushed master from c81b1fc0f8 to a2e9572681 3 months ago
isuri reviewed 3 months ago
"FROM DM_DEVICE_TAG_MAPPING dtm " +
"JOIN DM_TAG t ON dtm.TAG_ID = t.ID " +
"WHERE t.NAME IN (";
for (int i = 0; i < tagList.size(); i++) {

For more readability you can use Collections.nCopies to construct an immutable list of same object, in this case the question mark is the object.
https://www.geeksforgeeks.org/collections-ncopies-java/
and use String.join to concatenate the the elements with your prefered delimter, which is the comma in this case.
https://www.geeksforgeeks.org/java-string-join-examples/

For more readability you can use Collections.nCopies to construct an immutable list of same object, in this case the question mark is the object. https://www.geeksforgeeks.org/collections-ncopies-java/ and use String.join to concatenate the the elements with your prefered delimter, which is the comma in this case. https://www.geeksforgeeks.org/java-string-join-examples/
Gimhan-minion marked this conversation as resolved
isuri reviewed 3 months ago
FOREIGN KEY (ENROLMENT_ID) REFERENCES DM_ENROLMENT(ID),
FOREIGN KEY (TAG_ID) REFERENCES DM_TAG(ID) ON DELETE CASCADE
);
-- END OF DM_DEVICE_TAG_MAPPING TABLE --

Please add these scripts as compatible for each database also in the dbscripts situated at /device-mgt-core/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/dbscripts/cdm

Please add these scripts as compatible for each database also in the dbscripts situated at /device-mgt-core/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/dbscripts/cdm
Gimhan-minion marked this conversation as resolved
Gimhan-minion force-pushed master from a2e9572681 to 295f1831d8 3 months ago
inosh reviewed 3 months ago
sb.append("]}\n");
return sb.toString();
}
Owner

Extra spaces

Extra spaces
Gimhan-minion marked this conversation as resolved
Gimhan-minion force-pushed master from fc5716ddb4 to 8d2fb5fa76 3 months ago
Gimhan-minion requested review from charithag 3 months ago
ThilinaPremachandra reviewed 3 months ago
import io.entgra.device.mgt.core.apimgt.annotations.Scopes;
import io.entgra.device.mgt.core.device.mgt.api.jaxrs.util.Constants;
import javax.ws.rs.*;

Don't use wild card imports

Don't use wild card imports
Gimhan-minion marked this conversation as resolved
Gimhan-minion force-pushed master from 3a6cead8c9 to cfa40c0d2b 3 months ago
tcdlpds merged commit fa4d466798 into master 3 months ago

Reviewers

tcdlpds requested changes 3 months ago
thameera requested changes 3 months ago
charithag was requested for review 3 months ago
The pull request has been merged as fa4d466798.
Sign in to join this conversation.
No Milestone
No project
No Assignees
8 Participants
Notifications
Due Date

No due date set.

Dependencies

No dependencies set.

Reference: community/device-mgt-core#492
Loading…
There is no content yet.