This is an automated email from the ASF dual-hosted git repository.

lahirujayathilake pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git

commit ff923d32ce92da8aa699ec65584f91f8471c9b10
Author: lahiruj <[email protected]>
AuthorDate: Wed Oct 15 17:46:33 2025 -0400

    included AMIE account reactivate handler and its test class
---
 .../handler/RequestAccountReactivateHandler.java   |  96 +++++++++++
 .../amie/service/ProjectMembershipService.java     |  20 +++
 amie-decoder/src/main/proto/amie_packets.proto     |  20 ++-
 .../RequestAccountReactivateHandlerTest.java       | 176 +++++++++++++++++++++
 .../incoming-request.json                          |  19 +++
 .../outgoing-notify.json                           |  18 +++
 6 files changed, 345 insertions(+), 4 deletions(-)

diff --git 
a/amie-decoder/src/main/java/org/apache/custos/amie/handler/RequestAccountReactivateHandler.java
 
b/amie-decoder/src/main/java/org/apache/custos/amie/handler/RequestAccountReactivateHandler.java
new file mode 100644
index 000000000..4403cf82b
--- /dev/null
+++ 
b/amie-decoder/src/main/java/org/apache/custos/amie/handler/RequestAccountReactivateHandler.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.custos.amie.handler;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.apache.custos.amie.client.AmieClient;
+import org.apache.custos.amie.model.PacketEntity;
+import org.apache.custos.amie.service.ProjectMembershipService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Handles the 'request_account_reactivate' AMIE packet.
+ * <p>
+ * This transaction asks the local site to reactivate a user's association 
with a specific project.
+ * Upon successful local processing, it sends a 'notify_account_reactivate' 
reply.
+ */
+@Component
+public class RequestAccountReactivateHandler implements PacketHandler {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(RequestAccountReactivateHandler.class);
+
+    private final AmieClient amieClient;
+    private final ProjectMembershipService membershipService;
+
+    public RequestAccountReactivateHandler(AmieClient amieClient, 
ProjectMembershipService membershipService) {
+        this.amieClient = amieClient;
+        this.membershipService = membershipService;
+    }
+
+    @Override
+    public String supportsType() {
+        return "request_account_reactivate";
+    }
+
+    @Override
+    public void handle(JsonNode packetJson, PacketEntity packetEntity) {
+        LOGGER.info("Starting 'request_account_reactivate' handler for packet 
amie_id [{}].", packetEntity.getAmieId());
+
+        JsonNode body = packetJson.path("body");
+        String projectId = body.path("ProjectID").asText();
+        String personId = body.path("PersonID").asText();
+
+        Assert.hasText(projectId, "'ProjectID' must not be empty.");
+        Assert.hasText(personId, "'PersonID' must not be empty.");
+        LOGGER.info("Packet validated. Reactivating account for user [{}] on 
project [{}].", personId, projectId);
+
+        membershipService.reactivateMembershipsByPersonAndProject(projectId, 
personId);
+
+        sendSuccessReply(packetEntity.getAmieId(), body);
+
+        LOGGER.info("Successfully completed 'request_account_reactivate' 
handler and sent reply for packet amie_id [{}].", packetEntity.getAmieId());
+    }
+
+    private void sendSuccessReply(long packetRecId, JsonNode body) {
+        Map<String, Object> reply = new HashMap<>();
+        Map<String, Object> bodyContent = new HashMap<>();
+
+        bodyContent.put("ProjectID", body.path("ProjectID").asText());
+        bodyContent.put("PersonID", body.path("PersonID").asText());
+
+        List<String> resourceList = new ArrayList<>();
+        JsonNode rlNode = body.path("ResourceList");
+        if (rlNode.isArray()) {
+            rlNode.forEach(node -> resourceList.add(node.asText()));
+        }
+        bodyContent.put("ResourceList", resourceList);
+
+        reply.put("type", "notify_account_reactivate");
+        reply.put("body", bodyContent);
+
+        amieClient.replyToPacket(packetRecId, reply);
+    }
+}
diff --git 
a/amie-decoder/src/main/java/org/apache/custos/amie/service/ProjectMembershipService.java
 
b/amie-decoder/src/main/java/org/apache/custos/amie/service/ProjectMembershipService.java
index 9b9de3770..df2ef664b 100644
--- 
a/amie-decoder/src/main/java/org/apache/custos/amie/service/ProjectMembershipService.java
+++ 
b/amie-decoder/src/main/java/org/apache/custos/amie/service/ProjectMembershipService.java
@@ -136,4 +136,24 @@ public class ProjectMembershipService {
         LOGGER.info("Reactivated {} PI memberships for project [{}]", 
piMemberships.size(), projectId);
     }
 
+    /**
+     * Reactivates memberships for a specific person on a project.
+     *
+     * @param projectId The project ID.
+     * @param personId  Person ID
+     */
+    @Transactional
+    public void reactivateMembershipsByPersonAndProject(String projectId, 
String personId) {
+        List<ProjectMembershipEntity> memberships = 
membershipRepository.findByProjectIdAndClusterAccount_Person_Id(projectId, 
personId);
+
+        if (memberships.isEmpty()) {
+            LOGGER.warn("No memberships found for person [{}] on project [{}]. 
No action taken.", personId, projectId);
+            return;
+        }
+
+        memberships.forEach(membership -> membership.setActive(true));
+        membershipRepository.saveAll(memberships);
+        LOGGER.info("Reactivated {} membership(s) for person [{}] on project 
[{}]", memberships.size(), personId, projectId);
+    }
+
 }
diff --git a/amie-decoder/src/main/proto/amie_packets.proto 
b/amie-decoder/src/main/proto/amie_packets.proto
index 804c39e96..760273203 100644
--- a/amie-decoder/src/main/proto/amie_packets.proto
+++ b/amie-decoder/src/main/proto/amie_packets.proto
@@ -14,8 +14,9 @@ enum PacketType {
   REQUEST_PROJECT_REACTIVATE = 3;
   REQUEST_ACCOUNT_CREATE = 4;
   REQUEST_ACCOUNT_INACTIVATE = 5;
-  REQUEST_USER_MODIFY = 6;
-  REQUEST_PERSON_MERGE = 7;
+  REQUEST_ACCOUNT_REACTIVATE = 6;
+  REQUEST_USER_MODIFY = 7;
+  REQUEST_PERSON_MERGE = 8;
 }
 
 // Expected reply descriptor from the AMIE header
@@ -114,6 +115,16 @@ message RequestAccountInactivate {
   google.protobuf.Struct extra_fields = 99;
 }
 
+// request_account_reactivate
+message RequestAccountReactivate {
+  string project_id = 1;                // "ProjectID"
+  string person_id = 2;                 // "PersonID"
+  repeated string resource_list = 3;    // "ResourceList"
+  string allocated_resource = 4;
+
+  google.protobuf.Struct extra_fields = 99;
+}
+
 // request_user_modify
 message RequestUserModify {
   string action_type = 1;             // "ActionType" (e.g., "replace" or 
"delete")
@@ -158,8 +169,9 @@ message Packet {
     RequestProjectReactivate request_project_reactivate = 12;
     RequestAccountCreate request_account_create = 13;
     RequestAccountInactivate request_account_inactivate = 14;
-    RequestUserModify request_user_modify = 15;
-    RequestPersonMerge request_person_merge = 16;
+    RequestAccountReactivate request_account_reactivate = 15;
+    RequestUserModify request_user_modify = 16;
+    RequestPersonMerge request_person_merge = 17;
 
     // A fallback for any packet types this schema doesn't support
     google.protobuf.Struct unknown_body = 50;
diff --git 
a/amie-decoder/src/test/java/org/apache/custos/amie/handler/RequestAccountReactivateHandlerTest.java
 
b/amie-decoder/src/test/java/org/apache/custos/amie/handler/RequestAccountReactivateHandlerTest.java
new file mode 100644
index 000000000..52967841e
--- /dev/null
+++ 
b/amie-decoder/src/test/java/org/apache/custos/amie/handler/RequestAccountReactivateHandlerTest.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.custos.amie.handler;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.custos.amie.client.AmieClient;
+import org.apache.custos.amie.model.PacketEntity;
+import org.apache.custos.amie.service.ProjectMembershipService;
+import org.apache.custos.amie.util.JsonTestUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+@Tag("unit")
+class RequestAccountReactivateHandlerTest {
+
+    @Mock
+    private AmieClient amieClient;
+
+    @Mock
+    private ProjectMembershipService membershipService;
+
+    private RequestAccountReactivateHandler handler;
+    private ObjectMapper objectMapper;
+
+    @BeforeEach
+    void setUp() {
+        handler = new RequestAccountReactivateHandler(amieClient, 
membershipService);
+        objectMapper = new ObjectMapper();
+    }
+
+    @Test
+    void supportsType_shouldReturnCorrectType() {
+        
assertThat(handler.supportsType()).isEqualTo("request_account_reactivate");
+    }
+
+    @Test
+    void handle_withValidPacket_shouldProcessSuccessfully() throws Exception {
+        JsonNode incomingPacket = 
JsonTestUtils.loadMockPacket("request_account_reactivate", 
"incoming-request.json");
+        JsonNode expectedReply = 
JsonTestUtils.loadMockPacket("request_account_reactivate", 
"outgoing-notify.json");
+
+        PacketEntity packetEntity = new PacketEntity();
+        packetEntity.setAmieId(233497923L);
+        packetEntity.setType("request_account_reactivate");
+
+        handler.handle(incomingPacket, packetEntity);
+
+        
verify(membershipService).reactivateMembershipsByPersonAndProject("test-project-456",
 "test-user-person-123");
+
+        @SuppressWarnings("unchecked")
+        ArgumentCaptor<Map<String, Object>> replyCaptor = 
ArgumentCaptor.forClass(Map.class);
+        verify(amieClient).replyToPacket(eq(233497923L), 
replyCaptor.capture());
+
+        Map<String, Object> sentReply = replyCaptor.getValue();
+        assertThat(sentReply).containsKey("type");
+        
assertThat(sentReply.get("type")).isEqualTo("notify_account_reactivate");
+        assertThat(sentReply).containsKey("body");
+
+        @SuppressWarnings("unchecked")
+        Map<String, Object> sentBody = (Map<String, Object>) 
sentReply.get("body");
+        JsonNode expectedBody = expectedReply.path("body");
+
+        assertThat(sentBody).containsKey("ProjectID");
+        assertThat(sentBody).containsKey("PersonID");
+        assertThat(sentBody).containsKey("ResourceList");
+
+        
assertThat(sentBody.get("ProjectID")).isEqualTo(expectedBody.path("ProjectID").asText());
+        
assertThat(sentBody.get("PersonID")).isEqualTo(expectedBody.path("PersonID").asText());
+    }
+
+    @Test
+    void handle_withMissingProjectId_shouldThrowException() {
+        JsonNode packetJson = createPacketJsonWithMissingField("ProjectID");
+        PacketEntity packetEntity = createPacketEntity();
+
+        assertThatThrownBy(() -> handler.handle(packetJson, packetEntity))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("'ProjectID' must not be empty");
+    }
+
+    @Test
+    void handle_withMissingPersonId_shouldThrowException() {
+        JsonNode packetJson = createPacketJsonWithMissingField("PersonID");
+        PacketEntity packetEntity = createPacketEntity();
+
+        assertThatThrownBy(() -> handler.handle(packetJson, packetEntity))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("'PersonID' must not be empty");
+    }
+
+    @Test
+    void handle_withEmptyResourceList_shouldProcessSuccessfully() {
+        JsonNode packetJson = createValidPacketJson();
+        PacketEntity packetEntity = createPacketEntity();
+
+        handler.handle(packetJson, packetEntity);
+
+        
verify(membershipService).reactivateMembershipsByPersonAndProject("test-project-456",
 "test-user-person-123");
+        verify(amieClient).replyToPacket(eq(12345L), any(Map.class));
+    }
+
+    @Test
+    void handle_withNonEmptyResourceList_shouldProcessSuccessfully() {
+        JsonNode packetJson = createPacketJsonWithResourceList();
+        PacketEntity packetEntity = createPacketEntity();
+
+        handler.handle(packetJson, packetEntity);
+
+        
verify(membershipService).reactivateMembershipsByPersonAndProject("test-project-456",
 "test-user-person-123");
+        verify(amieClient).replyToPacket(eq(12345L), any(Map.class));
+    }
+
+    private JsonNode createValidPacketJson() {
+        return objectMapper.createObjectNode().set("body", 
objectMapper.createObjectNode()
+                .put("ProjectID", "test-project-456")
+                .put("PersonID", "test-user-person-123")
+                .set("ResourceList", objectMapper.createArrayNode()));
+    }
+
+    private JsonNode createPacketJsonWithMissingField(String missingField) {
+        ObjectNode body = objectMapper.createObjectNode()
+                .put("ProjectID", "test-project-456")
+                .put("PersonID", "test-user-person-123")
+                .set("ResourceList", objectMapper.createArrayNode());
+
+        if (body.has(missingField)) {
+            body.remove(missingField);
+        }
+
+        return objectMapper.createObjectNode().set("body", body);
+    }
+
+    private JsonNode createPacketJsonWithResourceList() {
+        return objectMapper.createObjectNode().set("body", 
objectMapper.createObjectNode()
+                .put("ProjectID", "test-project-456")
+                .put("PersonID", "test-user-person-123")
+                .set("ResourceList", 
objectMapper.createArrayNode().add("test-resource1.testsite.testorg").add("test-resource2.testsite.testorg")));
+    }
+
+    private PacketEntity createPacketEntity() {
+        PacketEntity entity = new PacketEntity();
+        entity.setAmieId(12345L);
+        entity.setType("request_account_reactivate");
+        return entity;
+    }
+}
diff --git 
a/amie-decoder/src/test/resources/mock-data/request_account_reactivate/incoming-request.json
 
b/amie-decoder/src/test/resources/mock-data/request_account_reactivate/incoming-request.json
new file mode 100644
index 000000000..9a470d59e
--- /dev/null
+++ 
b/amie-decoder/src/test/resources/mock-data/request_account_reactivate/incoming-request.json
@@ -0,0 +1,19 @@
+{
+  "type": "request_account_reactivate",
+  "header": {
+    "packet_rec_id": 233497923,
+    "packet_id": "233497923",
+    "site_code": "TestSite",
+    "remote_site_name": "TestSite",
+    "timestamp": "2025-10-15T21:30:00Z",
+    "version": "1.0"
+  },
+  "body": {
+    "ProjectID": "test-project-456",
+    "PersonID": "test-user-person-123",
+    "ResourceList": [
+      "test-resource1.testsite.testorg"
+    ],
+    "AllocatedResource": "test-resource1.testsite.testorg"
+  }
+}
diff --git 
a/amie-decoder/src/test/resources/mock-data/request_account_reactivate/outgoing-notify.json
 
b/amie-decoder/src/test/resources/mock-data/request_account_reactivate/outgoing-notify.json
new file mode 100644
index 000000000..d78529c8b
--- /dev/null
+++ 
b/amie-decoder/src/test/resources/mock-data/request_account_reactivate/outgoing-notify.json
@@ -0,0 +1,18 @@
+{
+  "type": "notify_account_reactivate",
+  "header": {
+    "packet_rec_id": 233497923,
+    "packet_id": "233497923",
+    "site_code": "TestSite",
+    "remote_site_name": "TestSite",
+    "timestamp": "2025-10-15T21:30:00Z",
+    "version": "1.0"
+  },
+  "body": {
+    "ProjectID": "test-project-456",
+    "PersonID": "test-user-person-123",
+    "ResourceList": [
+      "test-resource1.testsite.testorg"
+    ]
+  }
+}

Reply via email to