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()))
{