io.streamnative.pulsar.handlers.kop.schemaregistry.resources.SubjectResource Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pulsar-kafka-schema-registry Show documentation
Show all versions of pulsar-kafka-schema-registry Show documentation
Kafka Compatible Schema Registry
/**
* Copyright (c) 2019 - 2024 StreamNative, Inc.. All Rights Reserved.
*/
/**
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.streamnative.pulsar.handlers.kop.schemaregistry.resources;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.streamnative.pulsar.handlers.kop.schemaregistry.HttpJsonRequestProcessor;
import io.streamnative.pulsar.handlers.kop.schemaregistry.SchemaRegistryHandler;
import io.streamnative.pulsar.handlers.kop.schemaregistry.exceptions.Errors;
import io.streamnative.pulsar.handlers.kop.schemaregistry.exceptions.RestException;
import io.streamnative.pulsar.handlers.kop.schemaregistry.exceptions.SchemaRegistryException;
import io.streamnative.pulsar.handlers.kop.schemaregistry.model.CompatibilityChecker;
import io.streamnative.pulsar.handlers.kop.schemaregistry.model.LookupFilter;
import io.streamnative.pulsar.handlers.kop.schemaregistry.model.Schema;
import io.streamnative.pulsar.handlers.kop.schemaregistry.model.SchemaStorage;
import io.streamnative.pulsar.handlers.kop.schemaregistry.model.SchemaStorageAccessor;
import io.streamnative.pulsar.handlers.kop.schemaregistry.model.impl.SchemaStorageException;
import io.streamnative.pulsar.handlers.kop.schemaregistry.model.rest.CreateSchemaRequest;
import io.streamnative.pulsar.handlers.kop.schemaregistry.model.rest.SchemaReference;
import io.streamnative.pulsar.handlers.kop.schemaregistry.utils.SubjectUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.pulsar.common.util.FutureUtil;
public class SubjectResource extends AbstractResource {
public SubjectResource(SchemaStorageAccessor schemaStorageAccessor,
String defaultNamespace) {
super(schemaStorageAccessor, defaultNamespace);
}
@Override
public void register(SchemaRegistryHandler schemaRegistryHandler) {
schemaRegistryHandler.addProcessor(new GetAllSubjects());
schemaRegistryHandler.addProcessor(new DeleteSubject());
schemaRegistryHandler.addProcessor(new LookupSchema());
}
@Data
@AllArgsConstructor
public static final class LookupResponseForSubject {
int id;
int version;
String subject;
String schema;
String type;
List references;
}
// GET /subjects
public class GetAllSubjects extends HttpJsonRequestProcessor> {
// default value is false
private static final String QUERY_DELETED = "deleted";
// default value is false
private static final String QUERY_DELETED_ONLY = "deletedOnly";
public GetAllSubjects() {
super(Void.class, "/subjects", GET);
}
@Override
protected CompletableFuture> processRequest(Void payload, List patternGroups,
FullHttpRequest request,
Map> queryParams,
String currentTenant)
throws Exception {
LookupFilter filter = LookupFilter.DEFAULT;
// if both deleted && deletedOnly are true, return deleted only
boolean lookupDeletedSubjects = getBooleanQueryParam(QUERY_DELETED, "false", queryParams);
boolean lookupDeletedOnlySubjects =
getBooleanQueryParam(QUERY_DELETED_ONLY, "false", queryParams);
if (lookupDeletedOnlySubjects) {
filter = LookupFilter.DELETED_ONLY;
} else if (lookupDeletedSubjects) {
filter = LookupFilter.INCLUDE_DELETED;
}
SchemaStorage schemaStorage = getSchemaStorage(currentTenant);
return schemaStorage.getAllSubjects(filter);
}
}
// DELETE /subjects/(string: subject)?permanent=(boolean: permanent)
public class DeleteSubject extends HttpJsonRequestProcessor> {
// default value is false
private static final String QUERY_PERMANENT_DELETE = "permanent";
public DeleteSubject() {
super(Void.class, "/subjects/" + STRING_PATTERN, DELETE);
}
@Override
protected CompletableFuture> processRequest(Void payload, List patternGroups,
FullHttpRequest request,
Map> queryParams,
String currentTenant)
throws Exception {
SchemaStorage schemaStorage = getSchemaStorage(currentTenant);
boolean permanentDelete = getBooleanQueryParam(QUERY_PERMANENT_DELETE, "false", queryParams);
final String subject = SubjectUtils.normalize(getString(0, patternGroups), defaultNamespace);
return schemaStorage.hasSubjects(subject, true).thenCompose(hasSubjects -> {
if (!hasSubjects) {
throw Errors.subjectNotFoundException(subject);
}
return CompletableFuture.completedFuture(null);
}).thenCompose(__ -> {
if (permanentDelete) {
return CompletableFuture.completedFuture(null);
}
return schemaStorage.hasSubjects(subject, false).thenCompose(hasSubjects -> {
if (!hasSubjects) {
throw Errors.subjectSoftDeletedException(subject);
}
return CompletableFuture.completedFuture(null);
});
}).thenCompose(__ -> schemaStorage.deleteSubject(subject, permanentDelete));
}
}
// POST /subjects/(string: subject)
public class LookupSchema
extends HttpJsonRequestProcessor {
// Type: boolean, default false
private static final String QUERY_PARAM_NORMALIZE = "normalize";
// Type: boolean, default false
private static final String QUERY_PARAM_LOOKUP_DELETED_SCHEMA = "deleted";
public LookupSchema() {
super(CreateSchemaRequest.class, "/subjects/" + STRING_PATTERN, POST);
}
@Override
protected CompletableFuture processRequest(CreateSchemaRequest payload,
List patternGroups,
FullHttpRequest request,
Map> queryParams,
String currentTenant)
throws Exception {
final String subject = SubjectUtils.normalize(getString(0, patternGroups), defaultNamespace);
final boolean normalize = getBooleanQueryParam(QUERY_PARAM_NORMALIZE, "false", queryParams);
final boolean lookupDeletedSchema = getBooleanQueryParam(QUERY_PARAM_LOOKUP_DELETED_SCHEMA, "false",
queryParams);
Schema schema = Schema
.builder()
.schema(payload.getSchema())
.type(payload.getSchemaType())
.references(payload.getReferences())
.subject(subject)
.build();
SchemaStorage schemaStorage = getSchemaStorage(currentTenant);
return schemaStorage.lookUpSchemaUnderSubject(subject, schema, normalize, lookupDeletedSchema)
.thenCompose(matchingSchema -> {
if (matchingSchema == null) {
return schemaStorage.hasSubjects(subject, true)
.thenCompose(exists -> {
if (!exists) {
return FutureUtil.failedFuture(
Errors.subjectNotFoundException(subject));
}
return FutureUtil.failedFuture(Errors.schemaNotFoundException());
});
}
return CompletableFuture.completedFuture(
new LookupResponseForSubject(
matchingSchema.getId(),
matchingSchema.getVersion(),
matchingSchema.getSubject(),
matchingSchema.getSchema(),
matchingSchema.getType(),
matchingSchema.getReferences()));
}).exceptionally(err -> {
while (err instanceof CompletionException) {
err = err.getCause();
}
if (err instanceof SchemaRegistryException) {
throw new RestException("Error while looking up schema under subject " + subject,
HttpResponseStatus.BAD_REQUEST.code(), HttpResponseStatus.INTERNAL_SERVER_ERROR.code());
}
if (err instanceof CompatibilityChecker.IncompatibleSchemaChangeException) {
throw new CompletionException(
new SchemaStorageException(err.getMessage(), HttpResponseStatus.CONFLICT));
} else {
throw new CompletionException(err);
}
});
}
}
}