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

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


The following commit(s) were added to refs/heads/master by this push:
     new c941eb2a4f fix(research-service): correct data-product registration 
and replica round-trip
c941eb2a4f is described below

commit c941eb2a4f7b2a61c3c6c8975038625662c743e0
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Tue Jun 9 01:23:42 2026 -0400

    fix(research-service): correct data-product registration and replica 
round-trip
    
    Four proto-migration regressions in the data-product path: (1) an unset 
parent_product_uri defaults to "" (not null) under proto3, wrongly firing the 
parent-Collection check so no top-level product could register; (2) 
dataProductToModel called putAll on the proto builder's immutable map view 
(UnsupportedOperationException on every read) and dropped replicaLocations; (3) 
dataProductToEntity dropped replicaLocations so products persisted with no 
replica/file path; (4) replica ids were g [...]
---
 .../airavata/research/mapper/ResearchMapper.java   | 79 +++++++++++++++++++++-
 .../research/repository/DataProductRepository.java | 11 ++-
 2 files changed, 86 insertions(+), 4 deletions(-)

diff --git 
a/airavata-api/research-service/src/main/java/org/apache/airavata/research/mapper/ResearchMapper.java
 
b/airavata-api/research-service/src/main/java/org/apache/airavata/research/mapper/ResearchMapper.java
index c75db099b5..f4db200b6f 100644
--- 
a/airavata-api/research-service/src/main/java/org/apache/airavata/research/mapper/ResearchMapper.java
+++ 
b/airavata-api/research-service/src/main/java/org/apache/airavata/research/mapper/ResearchMapper.java
@@ -121,12 +121,85 @@ public interface ResearchMapper extends 
CommonMapperConversions {
     NotificationEntity notificationToEntity(Notification model);
 
     // --- DataProductModel ---
-    DataProductModel dataProductToModel(DataProductEntity entity);
+    // Hand-written rather than MapStruct-generated: a protobuf Builder's map 
getter
+    // returns an immutable view, so the generated 
`builder.getProductMetadata().putAll(..)`
+    // throws UnsupportedOperationException. Use the builder's putAll* 
accessor instead,
+    // and map the repeated replicaLocations (which MapStruct silently drops). 
Same proto
+    // hand-mapping pattern as appDeploymentToModel above.
+    default DataProductModel dataProductToModel(DataProductEntity entity) {
+        if (entity == null) return null;
+        DataProductModel.Builder builder = DataProductModel.newBuilder();
+        if (entity.getProductUri() != null) 
builder.setProductUri(entity.getProductUri());
+        if (entity.getGatewayId() != null) 
builder.setGatewayId(entity.getGatewayId());
+        if (entity.getParentProductUri() != null) 
builder.setParentProductUri(entity.getParentProductUri());
+        if (entity.getProductName() != null) 
builder.setProductName(entity.getProductName());
+        if (entity.getProductDescription() != null) 
builder.setProductDescription(entity.getProductDescription());
+        if (entity.getOwnerName() != null) 
builder.setOwnerName(entity.getOwnerName());
+        if (entity.getDataProductType() != null) 
builder.setDataProductType(entity.getDataProductType());
+        builder.setProductSize(entity.getProductSize());
+        builder.setCreationTime(timestampToLong(entity.getCreationTime()));
+        
builder.setLastModifiedTime(timestampToLong(entity.getLastModifiedTime()));
+        if (entity.getProductMetadata() != null) {
+            builder.putAllProductMetadata(entity.getProductMetadata());
+        }
+        if (entity.getReplicaLocations() != null) {
+            for (DataReplicaLocationEntity replica : 
entity.getReplicaLocations()) {
+                builder.addReplicaLocations(dataReplicaToModel(replica));
+            }
+        }
+        return builder.build();
+    }
 
-    DataProductEntity dataProductToEntity(DataProductModel model);
+    // Hand-written so the nested replicaLocations are mapped: MapStruct 
silently
+    // drops the proto repeated -> entity collection mapping, so a registered 
data
+    // product would persist with no replica (losing its file path).
+    default DataProductEntity dataProductToEntity(DataProductModel model) {
+        if (model == null) return null;
+        DataProductEntity entity = new DataProductEntity();
+        entity.setProductUri(model.getProductUri());
+        entity.setGatewayId(model.getGatewayId());
+        entity.setProductName(model.getProductName());
+        entity.setProductDescription(model.getProductDescription());
+        entity.setOwnerName(model.getOwnerName());
+        entity.setParentProductUri(model.getParentProductUri());
+        entity.setProductSize(model.getProductSize());
+        entity.setCreationTime(longToTimestamp(model.getCreationTime()));
+        
entity.setLastModifiedTime(longToTimestamp(model.getLastModifiedTime()));
+        entity.setDataProductType(model.getDataProductType());
+        if (model.getProductMetadataMap() != null) {
+            entity.setProductMetadata(new 
LinkedHashMap<>(model.getProductMetadataMap()));
+        }
+        List<DataReplicaLocationEntity> replicas = new ArrayList<>();
+        for (DataReplicaLocationModel replica : 
model.getReplicaLocationsList()) {
+            replicas.add(dataReplicaToEntity(replica));
+        }
+        entity.setReplicaLocations(replicas);
+        return entity;
+    }
 
     // --- DataReplicaLocationModel ---
-    DataReplicaLocationModel dataReplicaToModel(DataReplicaLocationEntity 
entity);
+    // Hand-written for the same proto immutable-map reason as 
dataProductToModel.
+    default DataReplicaLocationModel 
dataReplicaToModel(DataReplicaLocationEntity entity) {
+        if (entity == null) return null;
+        DataReplicaLocationModel.Builder builder = 
DataReplicaLocationModel.newBuilder();
+        if (entity.getReplicaId() != null) 
builder.setReplicaId(entity.getReplicaId());
+        if (entity.getProductUri() != null) 
builder.setProductUri(entity.getProductUri());
+        if (entity.getReplicaName() != null) 
builder.setReplicaName(entity.getReplicaName());
+        if (entity.getReplicaDescription() != null) 
builder.setReplicaDescription(entity.getReplicaDescription());
+        builder.setCreationTime(timestampToLong(entity.getCreationTime()));
+        
builder.setLastModifiedTime(timestampToLong(entity.getLastModifiedTime()));
+        builder.setValidUntilTime(timestampToLong(entity.getValidUntilTime()));
+        if (entity.getReplicaLocationCategory() != null)
+            
builder.setReplicaLocationCategory(entity.getReplicaLocationCategory());
+        if (entity.getReplicaPersistentType() != null)
+            
builder.setReplicaPersistentType(entity.getReplicaPersistentType());
+        if (entity.getStorageResourceId() != null) 
builder.setStorageResourceId(entity.getStorageResourceId());
+        if (entity.getFilePath() != null) 
builder.setFilePath(entity.getFilePath());
+        if (entity.getReplicaMetadata() != null) {
+            builder.putAllReplicaMetadata(entity.getReplicaMetadata());
+        }
+        return builder.build();
+    }
 
     DataReplicaLocationEntity dataReplicaToEntity(DataReplicaLocationModel 
model);
 
diff --git 
a/airavata-api/research-service/src/main/java/org/apache/airavata/research/repository/DataProductRepository.java
 
b/airavata-api/research-service/src/main/java/org/apache/airavata/research/repository/DataProductRepository.java
index c027e45e37..514fb47b41 100644
--- 
a/airavata-api/research-service/src/main/java/org/apache/airavata/research/repository/DataProductRepository.java
+++ 
b/airavata-api/research-service/src/main/java/org/apache/airavata/research/repository/DataProductRepository.java
@@ -98,7 +98,12 @@ public class DataProductRepository extends 
AbstractRepository<DataProductModel,
             throw new ReplicaCatalogException("Owner name and gateway ID 
should not be empty");
         }
 
+        // A top-level (parentless) data product has no parent URI. Protobuf 
string
+        // fields default to "" rather than null, so an unset 
parent_product_uri
+        // arrives here as an empty string; treat that the same as null and 
skip the
+        // parent-Collection check (otherwise every top-level registration 
fails).
         if (dataProductEntity.getParentProductUri() != null
+                && !dataProductEntity.getParentProductUri().isEmpty()
                 && (!isExists(dataProductEntity.getParentProductUri())
                         || 
!getDataProduct(dataProductEntity.getParentProductUri())
                                 .getDataProductType()
@@ -118,7 +123,11 @@ public class DataProductRepository extends 
AbstractRepository<DataProductModel,
             logger.debug("Populating the product URI for ReplicaLocations 
objects for the Data Product");
             
dataProductEntity.getReplicaLocations().forEach(dataReplicaLocationEntity -> {
                 dataReplicaLocationEntity.setProductUri(productUri);
-                if (dataReplicaLocationEntity.getReplicaId() == null) {
+                // proto3 string fields default to "" (not null), so a replica 
with no
+                // client-supplied id arrives with an empty REPLICA_ID. 
Generate one;
+                // otherwise every replica collides on the same empty primary 
key.
+                if (dataReplicaLocationEntity.getReplicaId() == null
+                        || dataReplicaLocationEntity.getReplicaId().isEmpty()) 
{
                     
dataReplicaLocationEntity.setReplicaId(UUID.randomUUID().toString());
                 }
                 if 
(!dataReplicaLocationRepository.isExists(dataReplicaLocationEntity.getReplicaId()))
 {

Reply via email to