Skip to content

Commit

Permalink
Created an option in the CCompat API to support groups via subject na…
Browse files Browse the repository at this point in the history
…ming convention (#5017)

* Support an option to use ccompat with group support via subject (subject=groupId:artifactId)

* Fix some unused imports

* Fix regression in ccompat listing subjects

* Removed unused import

* Add new config properties to docs
  • Loading branch information
EricWittmann authored Aug 14, 2024
1 parent 5ace058 commit 318744c
Show file tree
Hide file tree
Showing 10 changed files with 399 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@

package io.apicurio.registry.ccompat.rest.v7.impl;

import io.apicurio.common.apps.util.Pair;
import io.apicurio.registry.ccompat.dto.SchemaReference;
import io.apicurio.registry.ccompat.rest.error.ConflictException;
import io.apicurio.registry.ccompat.rest.error.UnprocessableEntityException;
import io.apicurio.registry.content.ContentHandle;
import io.apicurio.registry.model.GA;
import io.apicurio.registry.rest.v2.beans.ArtifactReference;
import io.apicurio.registry.rules.RuleApplicationType;
import io.apicurio.registry.rules.RuleViolationException;
Expand All @@ -31,6 +33,7 @@
import io.apicurio.registry.storage.dto.ArtifactMetaDataDto;
import io.apicurio.registry.storage.dto.ArtifactReferenceDto;
import io.apicurio.registry.storage.dto.ArtifactVersionMetaDataDto;
import io.apicurio.registry.storage.dto.SearchedArtifactDto;
import io.apicurio.registry.storage.dto.StoredArtifactDto;
import io.apicurio.registry.storage.impl.sql.RegistryContentUtils;
import io.apicurio.registry.types.ArtifactState;
Expand All @@ -40,6 +43,7 @@
import io.apicurio.registry.types.provider.ArtifactTypeUtilProvider;
import io.apicurio.registry.types.provider.ArtifactTypeUtilProviderFactory;
import jakarta.inject.Inject;
import jakarta.ws.rs.BadRequestException;
import org.apache.avro.AvroTypeException;
import org.apache.avro.SchemaParseException;
import org.apache.commons.codec.digest.DigestUtils;
Expand Down Expand Up @@ -101,7 +105,36 @@ public CCompatConfig getCconfig() {
return cconfig;
}

protected ArtifactMetaDataDto createOrUpdateArtifact(String subject, String schema, String artifactType, List<SchemaReference> references, String groupId) {
protected String toSubjectWithGroupConcat(String groupId, String artifactId) {
return (groupId == null ? "" : groupId) + cconfig.groupConcatSeparator + artifactId;
}

protected String toSubjectWithGroupConcat(SearchedArtifactDto dto) {
return toSubjectWithGroupConcat(dto.getGroupId(), dto.getId());
}

private Pair<String, String> toGAFromGroupConcatSubject(String subject) {
int sepIdx = subject.indexOf(cconfig.groupConcatSeparator);
if (sepIdx < 1) {
throw new BadRequestException("Invalid subject format. Should be: groupId" + cconfig.groupConcatSeparator + "artifactId");
}
String groupId = subject.substring(0, sepIdx);
String artifactId = subject.substring(sepIdx + cconfig.groupConcatSeparator.length());
return new Pair<>(groupId, artifactId);
}

protected GA getGA(String groupId, String subject) {
String gid = groupId;
String aid = subject;
if (cconfig.groupConcatEnabled) {
Pair<String, String> ga = toGAFromGroupConcatSubject(subject);
gid = ga.getLeft();
aid = ga.getRight();
}
return new GA(gid, aid);
}

protected ArtifactMetaDataDto createOrUpdateArtifact(String artifactId, String schema, String artifactType, List<SchemaReference> references, String groupId) {
ArtifactMetaDataDto res;
final List<ArtifactReferenceDto> parsedReferences = parseReferences(references, groupId);
final List<ArtifactReference> artifactReferences = parsedReferences.stream().map(dto -> ArtifactReference.builder().name(dto.getName()).groupId(dto.getGroupId()).artifactId(dto.getArtifactId()).version(dto.getVersion()).build()).collect(Collectors.toList());
Expand All @@ -110,12 +143,12 @@ protected ArtifactMetaDataDto createOrUpdateArtifact(String subject, String sche
ContentHandle schemaContent;
schemaContent = ContentHandle.create(schema);

if (!doesArtifactExist(subject, groupId)) {
rulesService.applyRules(groupId, subject, artifactType, schemaContent, RuleApplicationType.CREATE, artifactReferences, resolvedReferences);
res = storage.createArtifact(groupId, subject, null, artifactType, schemaContent, parsedReferences);
if (!doesArtifactExist(artifactId, groupId)) {
rulesService.applyRules(groupId, artifactId, artifactType, schemaContent, RuleApplicationType.CREATE, artifactReferences, resolvedReferences);
res = storage.createArtifact(groupId, artifactId, null, artifactType, schemaContent, parsedReferences);
} else {
rulesService.applyRules(groupId, subject, artifactType, schemaContent, RuleApplicationType.UPDATE, artifactReferences, resolvedReferences);
res = storage.updateArtifact(groupId, subject, null, artifactType, schemaContent, parsedReferences);
rulesService.applyRules(groupId, artifactId, artifactType, schemaContent, RuleApplicationType.UPDATE, artifactReferences, resolvedReferences);
res = storage.updateArtifact(groupId, artifactId, null, artifactType, schemaContent, parsedReferences);
}
} catch (RuleViolationException ex) {
if (ex.getRuleType() == RuleType.VALIDITY) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ public class CCompatConfig {
@Info(category = "ccompat", description = "Maximum number of Subjects returned (compatibility API)", availableSince = "2.4.2.Final")
Supplier<Integer> maxSubjects;

@ConfigProperty(name = "registry.ccompat.group-concat.enabled", defaultValue = "false")
@Info(category = "ccompat", description = "Enable group support via concatenation in subject (compatibility API)", availableSince = "2.6.2.Final")
public boolean groupConcatEnabled;

@ConfigProperty(name = "registry.ccompat.group-concat.separator", defaultValue = ":")
@Info(category = "ccompat", description = "Separator to use when group concatenation is enabled (compatibility API)", availableSince = "2.6.2.Final")
public String groupConcatSeparator;

public Supplier<Boolean> getCanonicalHashModeEnabled() {
return canonicalHashModeEnabled;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import io.apicurio.registry.content.ContentHandle;
import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck;
import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck;
import io.apicurio.registry.model.GA;
import io.apicurio.registry.rules.RuleViolationException;
import io.apicurio.registry.rules.UnprocessableSchemaException;
import io.apicurio.registry.storage.dto.ArtifactVersionMetaDataDto;
Expand All @@ -45,12 +46,15 @@ public class CompatibilityResourceImpl extends AbstractResource implements Compa
@Override
@Authorized(style = AuthorizedStyle.ArtifactOnly, level = AuthorizedLevel.Write)
public CompatibilityCheckResponse testCompatibilityBySubjectName(String subject, SchemaContent request, Boolean verbose, String groupId) throws Exception {
final GA ga = getGA(groupId, subject);

final boolean fverbose = verbose == null ? Boolean.FALSE : verbose;
try {
final List<String> versions = storage.getArtifactVersions(groupId, subject);
final List<String> versions = storage.getArtifactVersions(ga.getGroupId(), ga.getArtifactId());
for (String version : versions) {
final ArtifactVersionMetaDataDto artifactVersionMetaData = storage.getArtifactVersionMetaData(groupId, subject, version);
rulesService.applyRules(groupId, subject, version, artifactVersionMetaData.getType(), ContentHandle.create(request.getSchema()), Collections.emptyList(), Collections.emptyMap());
final ArtifactVersionMetaDataDto artifactVersionMetaData = storage.getArtifactVersionMetaData(ga.getGroupId(), ga.getArtifactId(), version);
rulesService.applyRules(ga.getGroupId(), ga.getArtifactId(), version, artifactVersionMetaData.getType(),
ContentHandle.create(request.getSchema()), Collections.emptyList(), Collections.emptyMap());
}
return CompatibilityCheckResponse.IS_COMPATIBLE;
} catch (RuleViolationException ex) {
Expand All @@ -68,11 +72,12 @@ public CompatibilityCheckResponse testCompatibilityBySubjectName(String subject,
@Authorized(style = AuthorizedStyle.ArtifactOnly, level = AuthorizedLevel.Write)
public CompatibilityCheckResponse testCompatibilityByVersion(String subject, String versionString, SchemaContent request, Boolean verbose, String groupId) throws Exception {
final boolean fverbose = verbose == null ? Boolean.FALSE : verbose;
final GA ga = getGA(groupId, subject);

return parseVersionString(subject, versionString, groupId, v -> {
return parseVersionString(ga.getArtifactId(), versionString, ga.getGroupId(), v -> {
try {
final ArtifactVersionMetaDataDto artifact = storage.getArtifactVersionMetaData(groupId, subject, v);
rulesService.applyRules(groupId, subject, v, artifact.getType(), ContentHandle.create(request.getSchema()), Collections.emptyList(), Collections.emptyMap());
final ArtifactVersionMetaDataDto artifact = storage.getArtifactVersionMetaData(ga.getGroupId(), ga.getArtifactId(), v);
rulesService.applyRules(ga.getGroupId(), ga.getArtifactId(), v, artifact.getType(), ContentHandle.create(request.getSchema()), Collections.emptyList(), Collections.emptyMap());
return CompatibilityCheckResponse.IS_COMPATIBLE;
} catch (RuleViolationException ex) {
if (fverbose) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import io.apicurio.registry.ccompat.rest.v7.ConfigResource;
import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck;
import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck;
import io.apicurio.registry.model.GA;
import io.apicurio.registry.rules.compatibility.CompatibilityLevel;
import io.apicurio.registry.storage.RuleNotFoundException;
import io.apicurio.registry.storage.dto.RuleConfigurationDto;
Expand Down Expand Up @@ -102,17 +103,19 @@ public CompatibilityLevelDto updateGlobalCompatibilityLevel(CompatibilityLevelDt
@Audited(extractParameters = {"0", AuditingConstants.KEY_ARTIFACT_ID, "1", AuditingConstants.KEY_RULE})
@Authorized(style = AuthorizedStyle.ArtifactOnly, level = AuthorizedLevel.Write)
public CompatibilityLevelDto updateSubjectCompatibilityLevel(String subject, CompatibilityLevelDto request, String groupId) {
final GA ga = getGA(groupId, subject);

updateCompatibilityLevel(request.getCompatibility(),
dto -> {
if (!doesArtifactRuleExist(subject, RuleType.COMPATIBILITY, groupId)) {
storage.createArtifactRule(groupId, subject, RuleType.COMPATIBILITY, dto);
if (!doesArtifactRuleExist(ga.getArtifactId(), RuleType.COMPATIBILITY, ga.getGroupId())) {
storage.createArtifactRule(ga.getGroupId(), ga.getArtifactId(), RuleType.COMPATIBILITY, dto);
} else {
storage.updateArtifactRule(groupId, subject, RuleType.COMPATIBILITY, dto);
storage.updateArtifactRule(ga.getGroupId(), ga.getArtifactId(), RuleType.COMPATIBILITY, dto);
}
},
() -> {
try {
storage.deleteArtifactRule(groupId, subject, RuleType.COMPATIBILITY);
storage.deleteArtifactRule(ga.getGroupId(), ga.getArtifactId(), RuleType.COMPATIBILITY);
} catch (RuleNotFoundException e) {
//Ignore, fail only when the artifact is not found
}
Expand All @@ -123,17 +126,19 @@ public CompatibilityLevelDto updateSubjectCompatibilityLevel(String subject, Com
@Override
@Authorized(style = AuthorizedStyle.ArtifactOnly, level = AuthorizedLevel.Read)
public CompatibilityLevelParamDto getSubjectCompatibilityLevel(String subject, String groupId) {
return getCompatibilityLevel(() -> storage.getArtifactRule(groupId, subject, RuleType.COMPATIBILITY).getConfiguration());
final GA ga = getGA(groupId, subject);
return getCompatibilityLevel(() -> storage.getArtifactRule(ga.getGroupId(), ga.getArtifactId(), RuleType.COMPATIBILITY).getConfiguration());
}

@Override
@Audited(extractParameters = {"0", AuditingConstants.KEY_ARTIFACT_ID})
@Authorized(style = AuthorizedStyle.ArtifactOnly, level = AuthorizedLevel.Write)
public CompatibilityLevelParamDto deleteSubjectCompatibility(String subject, String groupId) {
final GA ga = getGA(groupId, subject);
final CompatibilityLevelParamDto compatibilityLevel = getCompatibilityLevel(() ->
storage.getArtifactRule(groupId, subject, RuleType.COMPATIBILITY).getConfiguration());
storage.getArtifactRule(ga.getGroupId(), ga.getArtifactId(), RuleType.COMPATIBILITY).getConfiguration());
if (!CompatibilityLevel.NONE.name().equals(compatibilityLevel.getCompatibilityLevel())) {
storage.deleteArtifactRule(groupId, subject, RuleType.COMPATIBILITY);
storage.deleteArtifactRule(ga.getGroupId(), ga.getArtifactId(), RuleType.COMPATIBILITY);
}
return compatibilityLevel;
}
Expand Down
Loading

0 comments on commit 318744c

Please sign in to comment.