All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.streamnative.pulsar.handlers.kop.schemaregistry.resources.SubjectResource Maven / Gradle / Ivy

The newest version!
/**
 * 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);
                    }
                });
        }

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy