diff --git a/dao-api/src/main/java/com/linkedin/metadata/dao/exception/ModelValidationException.java b/dao-api/src/main/java/com/linkedin/metadata/dao/exception/ModelValidationException.java index f15fb7720..3cfd84e3c 100644 --- a/dao-api/src/main/java/com/linkedin/metadata/dao/exception/ModelValidationException.java +++ b/dao-api/src/main/java/com/linkedin/metadata/dao/exception/ModelValidationException.java @@ -1,5 +1,9 @@ package com.linkedin.metadata.dao.exception; +/** + * Exception thrown when the requested aspect is not defined in the asset model / there is invalid aspect present + * in the database that is not defined in the asset model. + */ public class ModelValidationException extends RuntimeException { public ModelValidationException(String message) { diff --git a/restli-resources/src/main/java/com/linkedin/metadata/restli/BaseAspectRoutingResource.java b/restli-resources/src/main/java/com/linkedin/metadata/restli/BaseAspectRoutingResource.java index 37befadb8..26d998acc 100644 --- a/restli-resources/src/main/java/com/linkedin/metadata/restli/BaseAspectRoutingResource.java +++ b/restli-resources/src/main/java/com/linkedin/metadata/restli/BaseAspectRoutingResource.java @@ -7,6 +7,7 @@ import com.linkedin.data.template.StringArray; import com.linkedin.data.template.UnionTemplate; import com.linkedin.metadata.dao.AspectKey; +import com.linkedin.metadata.dao.exception.ModelValidationException; import com.linkedin.metadata.dao.ingestion.AspectCallbackRegistry; import com.linkedin.metadata.dao.ingestion.AspectCallbackResponse; import com.linkedin.metadata.dao.ingestion.AspectCallbackRoutingClient; @@ -143,8 +144,12 @@ protected Task get(@Nonnull KEY id, @QueryParam(PARAM_ASPECTS) @Optional public Task getSnapshot(@ActionParam(PARAM_URN) @Nonnull String urnString, @ActionParam(PARAM_ASPECTS) @Optional @Nullable String[] aspectNames) { final URN urn = parseUrnParam(urnString); - return getSnapshot(urnString, aspectNames, - getResourceLix().testGetSnapshot(String.valueOf(urn), ModelUtils.getEntityType(urn))); + try { + return getSnapshot(urnString, aspectNames, + getResourceLix().testGetSnapshot(String.valueOf(urn), ModelUtils.getEntityType(urn))); + } catch (ModelValidationException e) { + throw RestliUtils.invalidArgumentsException(e.getMessage()); + } } @Nonnull @@ -202,27 +207,31 @@ protected Task getSnapshot(@ActionParam(PARAM_URN) @Nonnull String urn @Override public Task getAsset(@ActionParam(PARAM_URN) @Nonnull String urnString, @ActionParam(PARAM_ASPECTS) @Optional @Nullable String[] aspectNames) { + try { + return RestliUtils.toTask(() -> { + final URN urn = parseUrnParam(urnString); + final Set> aspectClasses = parseAspectsParam(aspectNames, true); - return RestliUtils.toTask(() -> { - final URN urn = parseUrnParam(urnString); - final Set> aspectClasses = parseAspectsParam(aspectNames, true); - - if (!containsRoutingAspect(aspectClasses)) { - // Get snapshot from Local DAO. - final List aspectUnions = getInternalAspectsFromLocalDao(urn, aspectClasses); - return ModelUtils.newAsset(_assetClass, urn, aspectUnions); - } else { - final Set> nonRoutingAspects = getNonRoutingAspects(aspectClasses); - final List aspectsFromLocalDao = getInternalAspectsFromLocalDao(urn, nonRoutingAspects); - final Set> routingAspects = getRoutingAspects(aspectClasses); - final List aspectsFromGms = routingAspects.stream() - .map(routingAspect -> getInternalAspectsFromGms(urn, routingAspect)) - .flatMap(List::stream) - .collect(Collectors.toList()); - return ModelUtils.newAsset(_assetClass, urn, - Stream.concat(aspectsFromGms.stream(), aspectsFromLocalDao.stream()).collect(Collectors.toList())); - } - }); + if (!containsRoutingAspect(aspectClasses)) { + // Get snapshot from Local DAO. + final List aspectUnions = getInternalAspectsFromLocalDao(urn, aspectClasses); + return ModelUtils.newAsset(_assetClass, urn, aspectUnions); + } else { + final Set> nonRoutingAspects = getNonRoutingAspects(aspectClasses); + final List aspectsFromLocalDao = + getInternalAspectsFromLocalDao(urn, nonRoutingAspects); + final Set> routingAspects = getRoutingAspects(aspectClasses); + final List aspectsFromGms = routingAspects.stream() + .map(routingAspect -> getInternalAspectsFromGms(urn, routingAspect)) + .flatMap(List::stream) + .collect(Collectors.toList()); + return ModelUtils.newAsset(_assetClass, urn, + Stream.concat(aspectsFromGms.stream(), aspectsFromLocalDao.stream()).collect(Collectors.toList())); + } + }); + } catch (ModelValidationException e) { + throw RestliUtils.invalidArgumentsException(e.getMessage()); + } } /** diff --git a/restli-resources/src/main/java/com/linkedin/metadata/restli/BaseEntityResource.java b/restli-resources/src/main/java/com/linkedin/metadata/restli/BaseEntityResource.java index f1eb7d683..32dfb9f1c 100644 --- a/restli-resources/src/main/java/com/linkedin/metadata/restli/BaseEntityResource.java +++ b/restli-resources/src/main/java/com/linkedin/metadata/restli/BaseEntityResource.java @@ -11,6 +11,7 @@ import com.linkedin.metadata.dao.BaseLocalDAO; import com.linkedin.metadata.dao.ListResult; import com.linkedin.metadata.dao.UrnAspectEntry; +import com.linkedin.metadata.dao.exception.ModelValidationException; import com.linkedin.metadata.dao.tracking.BaseTrackingManager; import com.linkedin.metadata.dao.utils.ModelUtils; import com.linkedin.metadata.events.IngestionMode; @@ -477,9 +478,13 @@ protected Task rawIngestAssetInternal(@Nonnull ASSET asset, @Nonnull public Task getSnapshot(@ActionParam(PARAM_URN) @Nonnull String urnString, @ActionParam(PARAM_ASPECTS) @Optional @Nullable String[] aspectNames) { - final URN urn = parseUrnParam(urnString); - return getSnapshot(urnString, aspectNames, - getResourceLix().testGetSnapshot(String.valueOf(urn), ModelUtils.getEntityType(urn))); + try { + final URN urn = parseUrnParam(urnString); + return getSnapshot(urnString, aspectNames, + getResourceLix().testGetSnapshot(String.valueOf(urn), ModelUtils.getEntityType(urn))); + } catch (ModelValidationException e) { + throw RestliUtils.invalidArgumentsException(e.getMessage()); + } } @Deprecated @@ -525,27 +530,30 @@ protected Task getSnapshot(@ActionParam(PARAM_URN) @Nonnull String urn @Nonnull public Task getAsset(@ActionParam(PARAM_URN) @Nonnull String urnString, @ActionParam(PARAM_ASPECTS) @Optional @Nullable String[] aspectNames) { + try { + return RestliUtils.toTask(() -> { + final URN urn = parseUrnParam(urnString); - return RestliUtils.toTask(() -> { - final URN urn = parseUrnParam(urnString); - - if (!getLocalDAO().exists(urn)) { - throw RestliUtils.resourceNotFoundException(); - } + if (!getLocalDAO().exists(urn)) { + throw RestliUtils.resourceNotFoundException(); + } - final Set> keys = parseAspectsParam(aspectNames, true).stream() - .map(aspectClass -> new AspectKey<>(aspectClass, urn, LATEST_VERSION)) - .collect(Collectors.toSet()); + final Set> keys = parseAspectsParam(aspectNames, true).stream() + .map(aspectClass -> new AspectKey<>(aspectClass, urn, LATEST_VERSION)) + .collect(Collectors.toSet()); - final List aspects = getLocalDAO().get(keys) - .values() - .stream() - .filter(java.util.Optional::isPresent) - .map(aspect -> ModelUtils.newAspectUnion(_internalAspectUnionClass, aspect.get())) - .collect(Collectors.toList()); + final List aspects = getLocalDAO().get(keys) + .values() + .stream() + .filter(java.util.Optional::isPresent) + .map(aspect -> ModelUtils.newAspectUnion(_internalAspectUnionClass, aspect.get())) + .collect(Collectors.toList()); - return ModelUtils.newAsset(_assetClass, urn, aspects); - }); + return ModelUtils.newAsset(_assetClass, urn, aspects); + }); + } catch (ModelValidationException e) { + throw RestliUtils.invalidArgumentsException(e.getMessage()); + } } /** diff --git a/restli-resources/src/test/java/com/linkedin/metadata/restli/BaseEntityResourceTest.java b/restli-resources/src/test/java/com/linkedin/metadata/restli/BaseEntityResourceTest.java index 52d2ffc3c..d2d5d1f23 100644 --- a/restli-resources/src/test/java/com/linkedin/metadata/restli/BaseEntityResourceTest.java +++ b/restli-resources/src/test/java/com/linkedin/metadata/restli/BaseEntityResourceTest.java @@ -11,6 +11,7 @@ import com.linkedin.metadata.dao.ListResult; import com.linkedin.metadata.dao.UrnAspectEntry; import com.linkedin.metadata.dao.builder.BaseLocalRelationshipBuilder.LocalRelationshipUpdates; +import com.linkedin.metadata.dao.exception.ModelValidationException; import com.linkedin.metadata.dao.internal.BaseGraphWriterDAO; import com.linkedin.metadata.dao.utils.ModelUtils; import com.linkedin.metadata.dao.utils.RecordUtils; @@ -713,6 +714,18 @@ public void testGetSnapshotWithOneAspect() { assertEquals(snapshot.getAspects().get(0).getAspectFoo(), foo); } + @Test + public void testGetSnapshotWithModelValidationException() { + FooUrn urn = makeFooUrn(1); + AspectFoo foo = new AspectFoo().setValue("foo"); + AspectKey fooKey = new AspectKey<>(AspectFoo.class, urn, LATEST_VERSION); + Set> aspectKeys = ImmutableSet.of(fooKey); + when(_mockLocalDAO.get(aspectKeys)).thenThrow(new ModelValidationException("model validation exception")); + String[] aspectNames = new String[]{ModelUtils.getAspectName(AspectFoo.class)}; + + assertThrows(RestLiServiceException.class, () -> runAndWait(_resource.getSnapshot(urn.toString(), aspectNames))); + } + @Test public void testGetSnapshotWithAllAspects() { FooUrn urn = makeFooUrn(1); @@ -1487,6 +1500,20 @@ public void testGetAsset() { assertEquals(asset.getAspectAttributes(), attributes); } + @Test + public void testGetAssetWithModelValidationException() { + FooUrn urn = makeFooUrn(1); + + AspectKey fooKey = new AspectKey<>(AspectFoo.class, urn, LATEST_VERSION); + + Set> aspectKeys = + ImmutableSet.of(fooKey); + when(_mockLocalDAO.get(aspectKeys)).thenThrow(new ModelValidationException("model validation exception")); + + assertThrows(RestLiServiceException.class, + () -> runAndWait(_resource.getAsset(urn.toString(), new String[] { "com.linkedin.testing.AspectFoo" }))); + } + @Test public void testGetNonExistAsset() { // Test get non existing assets