From d79f25bcb62afe977789187d61d0b3d4d2e39d91 Mon Sep 17 00:00:00 2001 From: Gabe Lyons Date: Tue, 2 Nov 2021 12:45:35 -0700 Subject: [PATCH] feat(graphql): extend entity client to support aspect methods directly via java (#3489) --- .../datahub/graphql/GmsClientFactory.java | 2 + .../datahub/graphql/GmsGraphQLEngine.java | 2 +- .../graphql/types/aspect/AspectType.java | 10 +- .../graphql/JavaEntityClientFactory.java | 7 +- .../linkedin/entity/client/EntityClient.java | 40 +++++ .../entity/client/JavaEntityClient.java | 86 +++++++++- .../entity/client/RestliEntityClient.java | 156 +++++++++++++++++- .../resources/entity/AspectResource.java | 45 +---- .../resources/entity/AspectUtils.java | 56 +++++++ 9 files changed, 353 insertions(+), 51 deletions(-) create mode 100644 metadata-service/restli-impl/src/main/java/com/linkedin/metadata/resources/entity/AspectUtils.java diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsClientFactory.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsClientFactory.java index 5bc168b061e195..c6b78269a7081b 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsClientFactory.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsClientFactory.java @@ -72,6 +72,8 @@ public static RestliEntityClient getEntitiesClient() { return _entities; } + // Deprecated- please use EntityClient from now on for all aspect related calls + @Deprecated public static AspectClient getAspectsClient() { if (_aspects == null) { synchronized (GmsClientFactory.class) { diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java index 42369154b9d58e..c36266f1fa81d9 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java @@ -235,7 +235,7 @@ public GmsGraphQLEngine( GmsClientFactory.getRelationshipsClient() ); this.glossaryTermType = new GlossaryTermType(entityClient); - this.aspectType = new AspectType(GmsClientFactory.getAspectsClient()); + this.aspectType = new AspectType(entityClient); this.usageType = new UsageType(GmsClientFactory.getUsageClient()); // Init Lists diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/aspect/AspectType.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/aspect/AspectType.java index c3d0de4dab2124..fe925663c52266 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/aspect/AspectType.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/aspect/AspectType.java @@ -3,7 +3,7 @@ import com.linkedin.datahub.graphql.VersionedAspectKey; import com.linkedin.datahub.graphql.QueryContext; import com.linkedin.datahub.graphql.generated.Aspect; -import com.linkedin.entity.client.AspectClient; +import com.linkedin.entity.client.EntityClient; import com.linkedin.metadata.aspect.VersionedAspect; import com.linkedin.r2.RemoteInvocationException; import com.linkedin.restli.client.RestLiResponseException; @@ -14,10 +14,10 @@ public class AspectType { - private final AspectClient _aspectClient; + private final EntityClient _entityClient; - public AspectType(final AspectClient aspectClient) { - _aspectClient = aspectClient; + public AspectType(final EntityClient entityClient) { + _entityClient = entityClient; } /** @@ -30,7 +30,7 @@ public List> batchLoad(@Nonnull List { try { - VersionedAspect entity = _aspectClient.getAspect(key.getUrn(), key.getAspectName(), key.getVersion(), context.getActor()); + VersionedAspect entity = _entityClient.getAspect(key.getUrn(), key.getAspectName(), key.getVersion(), context.getActor()); return DataFetcherResult.newResult().data(AspectMapper.map(entity)).build(); } catch (RemoteInvocationException e) { if (e instanceof RestLiResponseException) { diff --git a/metadata-service/graphql-api/src/main/java/com/datahub/metadata/graphql/JavaEntityClientFactory.java b/metadata-service/graphql-api/src/main/java/com/datahub/metadata/graphql/JavaEntityClientFactory.java index 84046e43614a00..9aed723bf0925d 100644 --- a/metadata-service/graphql-api/src/main/java/com/datahub/metadata/graphql/JavaEntityClientFactory.java +++ b/metadata-service/graphql-api/src/main/java/com/datahub/metadata/graphql/JavaEntityClientFactory.java @@ -4,6 +4,7 @@ import com.linkedin.metadata.entity.EntityService; import com.linkedin.metadata.search.EntitySearchService; import com.linkedin.metadata.search.SearchService; +import com.linkedin.metadata.timeseries.TimeseriesAspectService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; @@ -24,8 +25,12 @@ public class JavaEntityClientFactory { @Qualifier("entitySearchService") private EntitySearchService _entitySearchService; + @Autowired + @Qualifier("timeseriesAspectService") + private TimeseriesAspectService _timeseriesAspectService; + @Bean("javaEntityClient") public JavaEntityClient getJavaEntityClient() { - return new JavaEntityClient(_entityService, _entitySearchService, _searchService); + return new JavaEntityClient(_entityService, _entitySearchService, _searchService, _timeseriesAspectService); } } diff --git a/metadata-service/restli-client/src/main/java/com/linkedin/entity/client/EntityClient.java b/metadata-service/restli-client/src/main/java/com/linkedin/entity/client/EntityClient.java index a85fcf8ef941b3..f8d4b702fe4e85 100644 --- a/metadata-service/restli-client/src/main/java/com/linkedin/entity/client/EntityClient.java +++ b/metadata-service/restli-client/src/main/java/com/linkedin/entity/client/EntityClient.java @@ -1,8 +1,11 @@ package com.linkedin.entity.client; import com.linkedin.common.urn.Urn; +import com.linkedin.data.template.RecordTemplate; import com.linkedin.data.template.StringArray; import com.linkedin.entity.Entity; +import com.linkedin.metadata.aspect.EnvelopedAspect; +import com.linkedin.metadata.aspect.VersionedAspect; import com.linkedin.metadata.browse.BrowseResult; import com.linkedin.metadata.query.AutoCompleteResult; import com.linkedin.metadata.query.filter.Filter; @@ -10,10 +13,12 @@ import com.linkedin.metadata.query.filter.SortCriterion; import com.linkedin.metadata.search.SearchResult; import com.linkedin.metadata.query.ListUrnsResult; +import com.linkedin.mxe.MetadataChangeProposal; import com.linkedin.mxe.SystemMetadata; import com.linkedin.r2.RemoteInvocationException; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -218,4 +223,39 @@ public SearchResult filter( int count, @Nonnull String actor) throws RemoteInvocationException; + + public VersionedAspect getAspect( + @Nonnull String urn, + @Nonnull String aspect, + @Nonnull Long version, + @Nonnull String actor) + throws RemoteInvocationException; + + public VersionedAspect getAspectOrNull( + @Nonnull String urn, + @Nonnull String aspect, + @Nonnull Long version, + @Nonnull String actor) throws RemoteInvocationException; + + public List getTimeseriesAspectValues( + @Nonnull String urn, + @Nonnull String entity, + @Nonnull String aspect, + @Nullable Long startTimeMillis, + @Nullable Long endTimeMillis, + @Nullable Integer limit, + @Nullable String actor + ) throws RemoteInvocationException; + + public String ingestProposal( + @Nonnull final MetadataChangeProposal metadataChangeProposal, + @Nonnull final String actor + ) throws RemoteInvocationException; + + public Optional getVersionedAspect( + @Nonnull String urn, + @Nonnull String aspect, + @Nonnull Long version, + @Nonnull String actor, + @Nonnull Class aspectClass) throws RemoteInvocationException; } diff --git a/metadata-service/restli-client/src/main/java/com/linkedin/entity/client/JavaEntityClient.java b/metadata-service/restli-client/src/main/java/com/linkedin/entity/client/JavaEntityClient.java index 662ba32c698da3..c0389f6980a965 100644 --- a/metadata-service/restli-client/src/main/java/com/linkedin/entity/client/JavaEntityClient.java +++ b/metadata-service/restli-client/src/main/java/com/linkedin/entity/client/JavaEntityClient.java @@ -1,48 +1,67 @@ package com.linkedin.entity.client; import com.google.common.collect.ImmutableList; + import com.google.common.collect.ImmutableSet; +import com.linkedin.aspect.GetTimeseriesAspectValuesResponse; import com.linkedin.common.AuditStamp; import com.linkedin.common.urn.Urn; +import com.linkedin.data.DataMap; +import com.linkedin.data.template.RecordTemplate; import com.linkedin.data.template.StringArray; import com.linkedin.entity.Entity; +import com.linkedin.metadata.Constants; +import com.linkedin.metadata.aspect.EnvelopedAspect; +import com.linkedin.metadata.aspect.EnvelopedAspectArray; +import com.linkedin.metadata.aspect.VersionedAspect; import com.linkedin.metadata.browse.BrowseResult; +import com.linkedin.metadata.dao.utils.RecordUtils; import com.linkedin.metadata.entity.EntityService; import com.linkedin.metadata.query.AutoCompleteResult; import com.linkedin.metadata.query.filter.Filter; import com.linkedin.metadata.query.filter.SortCriterion; import com.linkedin.metadata.query.ListResult; import com.linkedin.metadata.query.ListUrnsResult; +import com.linkedin.metadata.resources.entity.AspectUtils; import com.linkedin.metadata.resources.entity.EntityResource; import com.linkedin.metadata.search.EntitySearchService; import com.linkedin.metadata.search.SearchResult; import com.linkedin.metadata.search.SearchService; +import com.linkedin.metadata.timeseries.TimeseriesAspectService; +import com.linkedin.mxe.MetadataChangeProposal; import com.linkedin.mxe.SystemMetadata; import com.linkedin.r2.RemoteInvocationException; import java.time.Clock; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; import static com.linkedin.metadata.search.utils.QueryUtils.*; +@Slf4j public class JavaEntityClient implements EntityClient { + private final Clock _clock = Clock.systemUTC(); + private EntityService _entityService; private EntitySearchService _entitySearchService; private SearchService _searchService; + private TimeseriesAspectService _timeseriesAspectService; public JavaEntityClient(@Nonnull final EntityService entityService, @Nonnull final EntitySearchService entitySearchService, @Nonnull final - SearchService searchService) { + SearchService searchService, @Nonnull final TimeseriesAspectService timeseriesAspectService) { _entityService = entityService; _entitySearchService = entitySearchService; _searchService = searchService; + _timeseriesAspectService = timeseriesAspectService; } @Nonnull @@ -285,4 +304,69 @@ public SearchResult filter(@Nonnull String entity, @Nonnull Filter filter, @Null int start, int count, @Nonnull String actor) throws RemoteInvocationException { return _entitySearchService.filter(entity, filter, sortCriterion, start, count); } + + @SneakyThrows + @Override + public VersionedAspect getAspect(@Nonnull String urn, @Nonnull String aspect, @Nonnull Long version, + @Nonnull String actor) throws RemoteInvocationException { + return _entityService.getVersionedAspect(Urn.createFromString(urn), aspect, version); + } + + @SneakyThrows + @Override + public VersionedAspect getAspectOrNull(@Nonnull String urn, @Nonnull String aspect, @Nonnull Long version, + @Nonnull String actor) throws RemoteInvocationException { + return _entityService.getVersionedAspect(Urn.createFromString(urn), aspect, version); + } + + @SneakyThrows + @Override + public List getTimeseriesAspectValues(@Nonnull String urn, @Nonnull String entity, + @Nonnull String aspect, @Nullable Long startTimeMillis, @Nullable Long endTimeMillis, @Nullable Integer limit, + @Nullable String actor) throws RemoteInvocationException { + GetTimeseriesAspectValuesResponse response = new GetTimeseriesAspectValuesResponse(); + response.setEntityName(entity); + response.setAspectName(aspect); + if (startTimeMillis != null) { + response.setStartTimeMillis(startTimeMillis); + } + if (endTimeMillis != null) { + response.setEndTimeMillis(endTimeMillis); + } + response.setLimit(limit); + response.setValues(new EnvelopedAspectArray( + _timeseriesAspectService.getAspectValues(Urn.createFromString(urn), entity, aspect, startTimeMillis, endTimeMillis, + limit))); + return response.getValues(); + } + + // TODO: Factor out ingest logic into a util that can be accessed by the java client and the resource + @SneakyThrows + @Override + public String ingestProposal(@Nonnull MetadataChangeProposal metadataChangeProposal, + @Nonnull String actor) throws RemoteInvocationException { + final AuditStamp auditStamp = + new AuditStamp().setTime(_clock.millis()).setActor(Urn.createFromString(Constants.UNKNOWN_ACTOR)); + final List additionalChanges = + AspectUtils.getAdditionalChanges(metadataChangeProposal, _entityService); + + Urn urn = _entityService.ingestProposal(metadataChangeProposal, auditStamp); + additionalChanges.forEach(proposal -> _entityService.ingestProposal(proposal, auditStamp)); + return urn.toString(); + } + + @SneakyThrows + @Override + public Optional getVersionedAspect(@Nonnull String urn, @Nonnull String aspect, + @Nonnull Long version, @Nonnull String actor, @Nonnull Class aspectClass) throws RemoteInvocationException { + VersionedAspect entity = _entityService.getVersionedAspect(Urn.createFromString(urn), aspect, version); + if (entity.hasAspect()) { + DataMap rawAspect = ((DataMap) entity.data().get("aspect")); + if (rawAspect.containsKey(aspectClass.getCanonicalName())) { + DataMap aspectDataMap = rawAspect.getDataMap(aspectClass.getCanonicalName()); + return Optional.of(RecordUtils.toRecordTemplate(aspectClass, aspectDataMap)); + } + } + return Optional.empty(); + } } diff --git a/metadata-service/restli-client/src/main/java/com/linkedin/entity/client/RestliEntityClient.java b/metadata-service/restli-client/src/main/java/com/linkedin/entity/client/RestliEntityClient.java index 3004ae9e738ac4..bfe11611111f48 100644 --- a/metadata-service/restli-client/src/main/java/com/linkedin/entity/client/RestliEntityClient.java +++ b/metadata-service/restli-client/src/main/java/com/linkedin/entity/client/RestliEntityClient.java @@ -2,7 +2,13 @@ import com.linkedin.common.client.BaseClient; import com.linkedin.common.urn.Urn; +import com.linkedin.data.DataMap; +import com.linkedin.data.template.RecordTemplate; import com.linkedin.data.template.StringArray; +import com.linkedin.entity.AspectsDoGetTimeseriesAspectValuesRequestBuilder; +import com.linkedin.entity.AspectsDoIngestProposalRequestBuilder; +import com.linkedin.entity.AspectsGetRequestBuilder; +import com.linkedin.entity.AspectsRequestBuilders; import com.linkedin.entity.EntitiesBatchGetRequestBuilder; import com.linkedin.entity.EntitiesDoAutocompleteRequestBuilder; import com.linkedin.entity.EntitiesDoBatchGetTotalEntityCountRequestBuilder; @@ -21,33 +27,42 @@ import com.linkedin.entity.EntitiesRequestBuilders; import com.linkedin.entity.Entity; import com.linkedin.entity.EntityArray; +import com.linkedin.metadata.aspect.EnvelopedAspect; +import com.linkedin.metadata.aspect.VersionedAspect; import com.linkedin.metadata.browse.BrowseResult; +import com.linkedin.metadata.dao.utils.RecordUtils; import com.linkedin.metadata.query.AutoCompleteResult; import com.linkedin.metadata.query.ListResult; import com.linkedin.metadata.query.filter.Filter; import com.linkedin.metadata.query.filter.SortCriterion; import com.linkedin.metadata.search.SearchResult; import com.linkedin.metadata.query.ListUrnsResult; +import com.linkedin.mxe.MetadataChangeProposal; import com.linkedin.mxe.SystemMetadata; import com.linkedin.r2.RemoteInvocationException; import com.linkedin.restli.client.Client; +import com.linkedin.restli.client.RestLiResponseException; +import com.linkedin.restli.common.HttpStatus; import java.net.URISyntaxException; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import lombok.extern.slf4j.Slf4j; import static com.linkedin.metadata.search.utils.QueryUtils.newFilter; - +@Slf4j public class RestliEntityClient extends BaseClient implements EntityClient { private static final EntitiesRequestBuilders ENTITIES_REQUEST_BUILDERS = new EntitiesRequestBuilders(); + private static final AspectsRequestBuilders ASPECTS_REQUEST_BUILDERS = new AspectsRequestBuilders(); public RestliEntityClient(@Nonnull final Client restliClient) { super(restliClient); @@ -399,4 +414,143 @@ public SearchResult filter(@Nonnull String entity, @Nonnull Filter filter, @Null } return sendClientRequest(requestBuilder, actor).getEntity(); } + + /** + * Gets aspect at version for an entity + * + * @param urn urn for the entity + * @return list of paths given urn + * @throws RemoteInvocationException on remote request error. + */ + @Nonnull + public VersionedAspect getAspect( + @Nonnull String urn, + @Nonnull String aspect, + @Nonnull Long version, + @Nonnull String actor) + throws RemoteInvocationException { + + AspectsGetRequestBuilder requestBuilder = + ASPECTS_REQUEST_BUILDERS.get().id(urn).aspectParam(aspect).versionParam(version); + + return sendClientRequest(requestBuilder, actor).getEntity(); + } + + /** + * Gets aspect at version for an entity, or null if one doesn't exist. + * + * @param urn urn for the entity + * @return list of paths given urn + * @throws RemoteInvocationException on remote request error. + */ + @Nullable + public VersionedAspect getAspectOrNull( + @Nonnull String urn, + @Nonnull String aspect, + @Nonnull Long version, + @Nonnull String actor) + throws RemoteInvocationException { + + AspectsGetRequestBuilder requestBuilder = + ASPECTS_REQUEST_BUILDERS.get().id(urn).aspectParam(aspect).versionParam(version); + try { + return sendClientRequest(requestBuilder, actor).getEntity(); + } catch (RestLiResponseException e) { + if (e.getStatus() == HttpStatus.S_404_NOT_FOUND.getCode()) { + // Then the aspect was not found. Return null. + return null; + } + throw e; + } + } + + /** + * Retrieve instances of a particular aspect. + * + * @param urn urn for the entity. + * @param entity the name of the entity. + * @param aspect the name of the aspect. + * @param startTimeMillis the earliest desired event time of the aspect value in milliseconds. + * @param endTimeMillis the latest desired event time of the aspect value in milliseconds. + * @param limit the maximum number of desired aspect values. + * @param actor the actor associated with the request [internal] + * @return the list of EnvelopedAspect values satisfying the input parameters. + * @throws RemoteInvocationException on remote request error. + */ + @Nonnull + public List getTimeseriesAspectValues( + @Nonnull String urn, + @Nonnull String entity, + @Nonnull String aspect, + @Nullable Long startTimeMillis, + @Nullable Long endTimeMillis, + @Nullable Integer limit, + @Nullable String actor + ) + throws RemoteInvocationException { + + AspectsDoGetTimeseriesAspectValuesRequestBuilder requestBuilder = + ASPECTS_REQUEST_BUILDERS.actionGetTimeseriesAspectValues() + .urnParam(urn) + .entityParam(entity) + .aspectParam(aspect); + + if (startTimeMillis != null) { + requestBuilder.startTimeMillisParam(startTimeMillis); + } + + if (endTimeMillis != null) { + requestBuilder.endTimeMillisParam(endTimeMillis); + } + + if (limit != null) { + requestBuilder.limitParam(limit); + } + return sendClientRequest(requestBuilder, actor).getEntity().getValues(); + } + + /** + * Ingest a MetadataChangeProposal event. + * @return + */ + public String ingestProposal(@Nonnull final MetadataChangeProposal metadataChangeProposal, @Nonnull final String actor) + throws RemoteInvocationException { + final AspectsDoIngestProposalRequestBuilder requestBuilder = ASPECTS_REQUEST_BUILDERS.actionIngestProposal() + .proposalParam(metadataChangeProposal); + return sendClientRequest(requestBuilder, actor).getEntity(); + } + + public Optional getVersionedAspect( + @Nonnull String urn, + @Nonnull String aspect, + @Nonnull Long version, + @Nonnull String actor, + @Nonnull Class aspectClass) + throws RemoteInvocationException { + + AspectsGetRequestBuilder requestBuilder = + ASPECTS_REQUEST_BUILDERS.get().id(urn).aspectParam(aspect).versionParam(version); + + try { + VersionedAspect entity = sendClientRequest(requestBuilder, actor).getEntity(); + if (entity.hasAspect()) { + DataMap rawAspect = ((DataMap) entity.data().get("aspect")); + if (rawAspect.containsKey(aspectClass.getCanonicalName())) { + DataMap aspectDataMap = rawAspect.getDataMap(aspectClass.getCanonicalName()); + return Optional.of(RecordUtils.toRecordTemplate(aspectClass, aspectDataMap)); + } + } + } catch (RestLiResponseException e) { + if (e.getStatus() == 404) { + log.debug("Could not find aspect {} for entity {}", aspect, urn); + return Optional.empty(); + } else { + // re-throw other exceptions + throw e; + } + } + + return Optional.empty(); + } + } diff --git a/metadata-service/restli-impl/src/main/java/com/linkedin/metadata/resources/entity/AspectResource.java b/metadata-service/restli-impl/src/main/java/com/linkedin/metadata/resources/entity/AspectResource.java index a70423eee9f015..e61a2b818855d5 100644 --- a/metadata-service/restli-impl/src/main/java/com/linkedin/metadata/resources/entity/AspectResource.java +++ b/metadata-service/restli-impl/src/main/java/com/linkedin/metadata/resources/entity/AspectResource.java @@ -1,12 +1,10 @@ package com.linkedin.metadata.resources.entity; import com.codahale.metrics.MetricRegistry; -import com.google.common.collect.ImmutableSet; + import com.linkedin.aspect.GetTimeseriesAspectValuesResponse; import com.linkedin.common.AuditStamp; import com.linkedin.common.urn.Urn; -import com.linkedin.data.template.RecordTemplate; -import com.linkedin.events.metadata.ChangeType; import com.linkedin.metadata.Constants; import com.linkedin.metadata.aspect.EnvelopedAspectArray; import com.linkedin.metadata.aspect.VersionedAspect; @@ -14,9 +12,6 @@ import com.linkedin.metadata.entity.ValidationException; import com.linkedin.metadata.restli.RestliUtil; import com.linkedin.metadata.timeseries.TimeseriesAspectService; -import com.linkedin.metadata.utils.EntityKeyUtils; -import com.linkedin.metadata.utils.GenericAspectUtils; -import com.linkedin.mxe.GenericAspect; import com.linkedin.mxe.MetadataChangeProposal; import com.linkedin.parseq.Task; import com.linkedin.restli.common.HttpStatus; @@ -32,11 +27,7 @@ import io.opentelemetry.extension.annotations.WithSpan; import java.net.URISyntaxException; import java.time.Clock; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.inject.Inject; @@ -135,7 +126,8 @@ public Task ingestProposal( // TODO: Use the actor present in the IC. final AuditStamp auditStamp = new AuditStamp().setTime(_clock.millis()).setActor(Urn.createFromString(Constants.UNKNOWN_ACTOR)); - final List additionalChanges = getAdditionalChanges(metadataChangeProposal); + final List additionalChanges = + AspectUtils.getAdditionalChanges(metadataChangeProposal, _entityService); return RestliUtil.toTask(() -> { log.debug("Proposal: {}", metadataChangeProposal); @@ -149,35 +141,4 @@ public Task ingestProposal( }, MetricRegistry.name(this.getClass(), "ingestProposal")); } - private List getAdditionalChanges(@Nonnull MetadataChangeProposal metadataChangeProposal) { - // No additional changes for delete operation - if (metadataChangeProposal.getChangeType() == ChangeType.DELETE) { - return Collections.emptyList(); - } - - final List additionalChanges = new ArrayList<>(); - - final Urn urn = EntityKeyUtils.getUrnFromProposal(metadataChangeProposal, - _entityService.getKeyAspectSpec(metadataChangeProposal.getEntityType())); - - return _entityService.generateDefaultAspectsIfMissing(urn, ImmutableSet.of(metadataChangeProposal.getAspectName())) - .stream() - .map(entry -> getProposalFromAspect(entry.getKey(), entry.getValue(), metadataChangeProposal)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } - - private MetadataChangeProposal getProposalFromAspect(String aspectName, RecordTemplate aspect, - MetadataChangeProposal original) { - try { - MetadataChangeProposal proposal = original.copy(); - GenericAspect genericAspect = GenericAspectUtils.serializeAspect(aspect); - proposal.setAspect(genericAspect); - proposal.setAspectName(aspectName); - return proposal; - } catch (CloneNotSupportedException e) { - log.error("Issue while generating additional proposals corresponding to the input proposal", e); - } - return null; - } } diff --git a/metadata-service/restli-impl/src/main/java/com/linkedin/metadata/resources/entity/AspectUtils.java b/metadata-service/restli-impl/src/main/java/com/linkedin/metadata/resources/entity/AspectUtils.java new file mode 100644 index 00000000000000..bc618f21d72d0d --- /dev/null +++ b/metadata-service/restli-impl/src/main/java/com/linkedin/metadata/resources/entity/AspectUtils.java @@ -0,0 +1,56 @@ +package com.linkedin.metadata.resources.entity; + +import com.google.common.collect.ImmutableSet; +import com.linkedin.common.urn.Urn; +import com.linkedin.data.template.RecordTemplate; +import com.linkedin.events.metadata.ChangeType; +import com.linkedin.metadata.entity.EntityService; +import com.linkedin.metadata.utils.EntityKeyUtils; +import com.linkedin.metadata.utils.GenericAspectUtils; +import com.linkedin.mxe.GenericAspect; +import com.linkedin.mxe.MetadataChangeProposal; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class AspectUtils { + + private AspectUtils() { } + + public static List getAdditionalChanges( + @Nonnull MetadataChangeProposal metadataChangeProposal, + @Nonnull EntityService entityService + ) { + // No additional changes for delete operation + if (metadataChangeProposal.getChangeType() == ChangeType.DELETE) { + return Collections.emptyList(); + } + + final Urn urn = EntityKeyUtils.getUrnFromProposal(metadataChangeProposal, + entityService.getKeyAspectSpec(metadataChangeProposal.getEntityType())); + + return entityService.generateDefaultAspectsIfMissing(urn, ImmutableSet.of(metadataChangeProposal.getAspectName())) + .stream() + .map(entry -> getProposalFromAspect(entry.getKey(), entry.getValue(), metadataChangeProposal)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private static MetadataChangeProposal getProposalFromAspect(String aspectName, RecordTemplate aspect, + MetadataChangeProposal original) { + try { + MetadataChangeProposal proposal = original.copy(); + GenericAspect genericAspect = GenericAspectUtils.serializeAspect(aspect); + proposal.setAspect(genericAspect); + proposal.setAspectName(aspectName); + return proposal; + } catch (CloneNotSupportedException e) { + log.error("Issue while generating additional proposals corresponding to the input proposal", e); + } + return null; + } +}