Skip to content

Commit

Permalink
[3.0] Forward port the "group concatenation" feature of the ccompat A…
Browse files Browse the repository at this point in the history
…PI introduced in 2.6.2 (#5189)

* Ported the "group concatenation" feature of the ccompat API introduced in 2.6.2 to main

* spotless:apply

* Generated docs
  • Loading branch information
EricWittmann authored Sep 17, 2024
1 parent 9a88ef5 commit 7d9875d
Show file tree
Hide file tree
Showing 9 changed files with 421 additions and 134 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
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;
Expand All @@ -19,6 +20,7 @@
import io.apicurio.registry.storage.dto.ContentWrapperDto;
import io.apicurio.registry.storage.dto.EditableArtifactMetaDataDto;
import io.apicurio.registry.storage.dto.EditableVersionMetaDataDto;
import io.apicurio.registry.storage.dto.SearchedArtifactDto;
import io.apicurio.registry.storage.dto.StoredArtifactVersionDto;
import io.apicurio.registry.storage.error.ArtifactNotFoundException;
import io.apicurio.registry.storage.error.RuleNotFoundException;
Expand All @@ -31,6 +33,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 @@ -63,7 +66,37 @@ public abstract class AbstractResource {
@Inject
ArtifactTypeUtilProviderFactory factory;

protected ArtifactVersionMetaDataDto createOrUpdateArtifact(String subject, String schema,
protected String toSubjectWithGroupConcat(String groupId, String artifactId) {
return (groupId == null ? "" : groupId) + cconfig.groupConcatSeparator + artifactId;
}

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

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 artifactId) {
String gid = groupId;
String aid = artifactId;
if (cconfig.groupConcatEnabled) {
Pair<String, String> ga = toGAFromGroupConcatSubject(artifactId);
gid = ga.getLeft();
aid = ga.getRight();
}
return new GA(gid, aid);
}

protected ArtifactVersionMetaDataDto createOrUpdateArtifact(String artifactId, String schema,
String artifactType, List<SchemaReference> references, String groupId) {
ArtifactVersionMetaDataDto res;
final List<ArtifactReferenceDto> parsedReferences = parseReferences(references, groupId);
Expand All @@ -80,9 +113,9 @@ protected ArtifactVersionMetaDataDto createOrUpdateArtifact(String subject, Stri
contentType = ContentTypes.APPLICATION_PROTOBUF;
}

if (!doesArtifactExist(subject, groupId)) {
if (!doesArtifactExist(artifactId, groupId)) {
TypedContent typedSchemaContent = TypedContent.create(schemaContent, contentType);
rulesService.applyRules(groupId, subject, artifactType, typedSchemaContent,
rulesService.applyRules(groupId, artifactId, artifactType, typedSchemaContent,
RuleApplicationType.CREATE, artifactReferences, resolvedReferences);

EditableArtifactMetaDataDto artifactMetaData = EditableArtifactMetaDataDto.builder().build();
Expand All @@ -91,15 +124,15 @@ protected ArtifactVersionMetaDataDto createOrUpdateArtifact(String subject, Stri
ContentWrapperDto firstVersionContent = ContentWrapperDto.builder().content(schemaContent)
.contentType(contentType).references(parsedReferences).build();

res = storage.createArtifact(groupId, subject, artifactType, artifactMetaData, null,
res = storage.createArtifact(groupId, artifactId, artifactType, artifactMetaData, null,
firstVersionContent, firstVersionMetaData, null, false).getValue();
} else {
TypedContent typedSchemaContent = TypedContent.create(schemaContent, contentType);
rulesService.applyRules(groupId, subject, artifactType, typedSchemaContent,
rulesService.applyRules(groupId, artifactId, artifactType, typedSchemaContent,
RuleApplicationType.UPDATE, artifactReferences, resolvedReferences);
ContentWrapperDto versionContent = ContentWrapperDto.builder().content(schemaContent)
.contentType(contentType).references(parsedReferences).build();
res = storage.createArtifactVersion(groupId, subject, null, artifactType, versionContent,
res = storage.createArtifactVersion(groupId, artifactId, null, artifactType, versionContent,
EditableVersionMetaDataDto.builder().build(), List.of(), false);
}
} catch (RuleViolationException ex) {
Expand All @@ -112,7 +145,7 @@ protected ArtifactVersionMetaDataDto createOrUpdateArtifact(String subject, Stri
return res;
}

protected ArtifactVersionMetaDataDto lookupSchema(String groupId, String subject, String schema,
protected ArtifactVersionMetaDataDto lookupSchema(String groupId, String artifactId, String schema,
List<SchemaReference> schemaReferences, String schemaType, boolean normalize) {
// FIXME simplify logic
try {
Expand All @@ -126,7 +159,7 @@ protected ArtifactVersionMetaDataDto lookupSchema(String groupId, String subject

if (cconfig.canonicalHashModeEnabled.get() || normalize) {
try {
amd = storage.getArtifactVersionMetaDataByContent(groupId, subject, true,
amd = storage.getArtifactVersionMetaDataByContent(groupId, artifactId, true,
typedSchemaContent, artifactReferences);
} catch (ArtifactNotFoundException ex) {
if (type.equals(ArtifactType.AVRO)) {
Expand All @@ -137,9 +170,9 @@ protected ArtifactVersionMetaDataDto lookupSchema(String groupId, String subject
// exception.
// This approach only works for schema types with dereference support (for now, only
// Avro in the ccompat API).
amd = storage.getArtifactVersions(groupId, subject).stream().filter(version -> {
amd = storage.getArtifactVersions(groupId, artifactId).stream().filter(version -> {
StoredArtifactVersionDto artifactVersion = storage
.getArtifactVersionContent(groupId, subject, version);
.getArtifactVersionContent(groupId, artifactId, version);
TypedContent typedArtifactVersion = TypedContent
.create(artifactVersion.getContent(), artifactVersion.getContentType());
Map<String, TypedContent> artifactVersionReferences = storage
Expand All @@ -149,17 +182,17 @@ protected ArtifactVersionMetaDataDto lookupSchema(String groupId, String subject
.dereference(typedArtifactVersion, artifactVersionReferences)
.getContent().content());
return dereferencedExistingContentSha.equals(DigestUtils.sha256Hex(schema));
}).findAny()
.map(version -> storage.getArtifactVersionMetaData(groupId, subject, version))
}).findAny().map(
version -> storage.getArtifactVersionMetaData(groupId, artifactId, version))
.orElseThrow(() -> ex);
} else {
throw ex;
}
}

} else {
amd = storage.getArtifactVersionMetaDataByContent(groupId, subject, false, typedSchemaContent,
artifactReferences);
amd = storage.getArtifactVersionMetaDataByContent(groupId, artifactId, false,
typedSchemaContent, artifactReferences);
}

return amd;
Expand Down Expand Up @@ -193,18 +226,18 @@ protected Map<String, TypedContent> resolveReferences(List<SchemaReference> refe
return resolvedReferences;
}

protected boolean isArtifactActive(String subject, String groupId) {
long count = storage.countActiveArtifactVersions(groupId, subject);
protected boolean isArtifactActive(String artifactId, String groupId) {
long count = storage.countActiveArtifactVersions(groupId, artifactId);
return count > 0;
}

protected String getLatestArtifactVersionForSubject(String subject, String groupId) {
protected String getLatestArtifactVersionForSubject(String artifactId, String groupId) {
try {
GAV latestGAV = storage.getBranchTip(new GA(groupId, subject), BranchId.LATEST,
GAV latestGAV = storage.getBranchTip(new GA(groupId, artifactId), BranchId.LATEST,
RetrievalBehavior.SKIP_DISABLED_LATEST);
return latestGAV.getRawVersionId();
} catch (ArtifactNotFoundException ex) {
throw new VersionNotFoundException(groupId, subject, "latest");
throw new VersionNotFoundException(groupId, artifactId, "latest");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,12 @@ public class CCompatConfig {
@Info(category = "ccompat", description = "Maximum number of Subjects returned (compatibility API)", availableSince = "2.4.2.Final")
Supplier<Integer> maxSubjects;

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

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.apicurio.registry.content.TypedContent;
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 @@ -30,21 +31,24 @@ public class CompatibilityResourceImpl extends AbstractResource implements Compa
@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.getRawGroupIdWithNull(),
ga.getRawArtifactId());
for (String version : versions) {
final ArtifactVersionMetaDataDto artifactVersionMetaData = storage
.getArtifactVersionMetaData(groupId, subject, version);
final ArtifactVersionMetaDataDto artifactVersionMetaData = storage.getArtifactVersionMetaData(
ga.getRawGroupIdWithNull(), ga.getRawArtifactId(), version);
// Assume the content type of the SchemaContent is the same as the previous version.
String contentType = ContentTypes.APPLICATION_JSON;
if (artifactVersionMetaData.getArtifactType().equals(ArtifactType.PROTOBUF)) {
contentType = ContentTypes.APPLICATION_PROTOBUF;
}
TypedContent typedContent = TypedContent.create(ContentHandle.create(request.getSchema()),
contentType);
rulesService.applyRules(groupId, subject, version, artifactVersionMetaData.getArtifactType(),
typedContent, Collections.emptyList(), Collections.emptyMap());
rulesService.applyRules(ga.getRawGroupIdWithNull(), ga.getRawArtifactId(), version,
artifactVersionMetaData.getArtifactType(), typedContent, Collections.emptyList(),
Collections.emptyMap());
}
return CompatibilityCheckResponse.IS_COMPATIBLE;
} catch (RuleViolationException ex) {
Expand All @@ -63,30 +67,33 @@ public CompatibilityCheckResponse testCompatibilityBySubjectName(String subject,
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 -> {
try {
final ArtifactVersionMetaDataDto artifact = storage.getArtifactVersionMetaData(groupId,
subject, v);
// Assume the content type of the SchemaContent is correct based on the artifact type.
String contentType = ContentTypes.APPLICATION_JSON;
if (artifact.getArtifactType().equals(ArtifactType.PROTOBUF)) {
contentType = ContentTypes.APPLICATION_PROTOBUF;
}
TypedContent typedContent = TypedContent.create(ContentHandle.create(request.getSchema()),
contentType);
rulesService.applyRules(groupId, subject, v, artifact.getArtifactType(), typedContent,
Collections.emptyList(), Collections.emptyMap());
return CompatibilityCheckResponse.IS_COMPATIBLE;
} catch (RuleViolationException ex) {
if (fverbose) {
return new CompatibilityCheckResponse(false, ex.getMessage());
} else {
return CompatibilityCheckResponse.IS_NOT_COMPATIBLE;
}
} catch (UnprocessableSchemaException ex) {
throw new UnprocessableEntityException(ex.getMessage());
}
});
return parseVersionString(ga.getRawArtifactId(), versionString, ga.getRawGroupIdWithNull(),
version -> {
try {
final ArtifactVersionMetaDataDto artifact = storage.getArtifactVersionMetaData(
ga.getRawGroupIdWithNull(), ga.getRawArtifactId(), version);
// Assume the content type of the SchemaContent is correct based on the artifact type.
String contentType = ContentTypes.APPLICATION_JSON;
if (artifact.getArtifactType().equals(ArtifactType.PROTOBUF)) {
contentType = ContentTypes.APPLICATION_PROTOBUF;
}
TypedContent typedContent = TypedContent
.create(ContentHandle.create(request.getSchema()), contentType);
rulesService.applyRules(ga.getRawGroupIdWithNull(), ga.getRawArtifactId(), version,
artifact.getArtifactType(), typedContent, Collections.emptyList(),
Collections.emptyMap());
return CompatibilityCheckResponse.IS_COMPATIBLE;
} catch (RuleViolationException ex) {
if (fverbose) {
return new CompatibilityCheckResponse(false, ex.getMessage());
} else {
return CompatibilityCheckResponse.IS_NOT_COMPATIBLE;
}
} catch (UnprocessableSchemaException ex) {
throw new UnprocessableEntityException(ex.getMessage());
}
});
}
}
Loading

0 comments on commit 7d9875d

Please sign in to comment.