com.hortonworks.registries.schemaregistry.SchemaVersionLifecycleManager.java Source code

Java tutorial

Introduction

Here is the source code for com.hortonworks.registries.schemaregistry.SchemaVersionLifecycleManager.java

Source

/*
 * Copyright 2016 Hortonworks.
 * <p>
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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 com.hortonworks.registries.schemaregistry;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.hortonworks.registries.common.QueryParam;
import com.hortonworks.registries.schemaregistry.cache.SchemaBranchCache;
import com.hortonworks.registries.schemaregistry.cache.SchemaVersionInfoCache;
import com.hortonworks.registries.schemaregistry.errors.IncompatibleSchemaException;
import com.hortonworks.registries.schemaregistry.errors.InvalidSchemaBranchVersionMapping;
import com.hortonworks.registries.schemaregistry.errors.InvalidSchemaException;
import com.hortonworks.registries.schemaregistry.errors.SchemaBranchNotFoundException;
import com.hortonworks.registries.schemaregistry.errors.SchemaNotFoundException;
import com.hortonworks.registries.schemaregistry.errors.SchemaVersionMergeException;
import com.hortonworks.registries.schemaregistry.errors.UnsupportedSchemaTypeException;
import com.hortonworks.registries.schemaregistry.state.CustomSchemaStateExecutor;
import com.hortonworks.registries.schemaregistry.state.InbuiltSchemaVersionLifecycleState;
import com.hortonworks.registries.schemaregistry.state.SchemaLifecycleException;
import com.hortonworks.registries.schemaregistry.state.SchemaVersionLifecycleContext;
import com.hortonworks.registries.schemaregistry.state.SchemaVersionLifecycleState;
import com.hortonworks.registries.schemaregistry.state.SchemaVersionLifecycleStateAction;
import com.hortonworks.registries.schemaregistry.state.SchemaVersionLifecycleStateMachine;
import com.hortonworks.registries.schemaregistry.state.SchemaVersionLifecycleStateTransition;
import com.hortonworks.registries.schemaregistry.state.SchemaVersionLifecycleStateTransitionListener;
import com.hortonworks.registries.schemaregistry.state.SchemaVersionLifecycleStates;
import com.hortonworks.registries.schemaregistry.state.SchemaVersionService;
import com.hortonworks.registries.schemaregistry.state.details.InitializedStateDetails;
import com.hortonworks.registries.schemaregistry.utils.ObjectMapperUtils;
import com.hortonworks.registries.storage.OrderByField;
import com.hortonworks.registries.storage.Storable;
import com.hortonworks.registries.storage.StorableKey;
import com.hortonworks.registries.storage.StorageManager;
import com.hortonworks.registries.storage.exception.StorageException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 *
 */
public class SchemaVersionLifecycleManager {
    private static final Logger LOG = LoggerFactory.getLogger(SchemaVersionLifecycleManager.class);

    private static final String DEFAULT_SCHEMA_REVIEW_EXECUTOR_CLASS = "com.hortonworks.registries.schemaregistry.state.DefaultCustomSchemaStateExecutor";
    public static final InbuiltSchemaVersionLifecycleState DEFAULT_VERSION_STATE = SchemaVersionLifecycleStates.INITIATED;
    private static final List<SchemaVersionLifecycleStateTransitionListener> DEFAULT_LISTENERS = new ArrayList<>();

    private final SchemaVersionLifecycleStateMachine schemaVersionLifecycleStateMachine;
    private CustomSchemaStateExecutor customSchemaStateExecutor;
    private SchemaVersionInfoCache schemaVersionInfoCache;
    private SchemaVersionRetriever schemaVersionRetriever;
    private static final int DEFAULT_RETRY_CT = 5;
    private StorageManager storageManager;
    private SchemaBranchCache schemaBranchCache;
    private HAServerNotificationManager haServerNotificationManager;
    private DefaultSchemaRegistry.SchemaMetadataFetcher schemaMetadataFetcher;

    public SchemaVersionLifecycleManager(StorageManager storageManager, Map<String, Object> props,
            DefaultSchemaRegistry.SchemaMetadataFetcher schemaMetadataFetcher, SchemaBranchCache schemaBranchCache,
            HAServerNotificationManager haServerNotificationManager) {
        this.storageManager = storageManager;
        this.schemaMetadataFetcher = schemaMetadataFetcher;
        this.schemaBranchCache = schemaBranchCache;
        this.haServerNotificationManager = haServerNotificationManager;
        SchemaVersionLifecycleStateMachine.Builder builder = SchemaVersionLifecycleStateMachine.newBuilder();

        DefaultSchemaRegistry.Options options = new DefaultSchemaRegistry.Options(props);
        schemaVersionRetriever = createSchemaVersionRetriever();

        schemaVersionInfoCache = new SchemaVersionInfoCache(schemaVersionRetriever, options.getMaxSchemaCacheSize(),
                options.getSchemaExpiryInSecs() * 1000L);

        customSchemaStateExecutor = createSchemaReviewExecutor(props, builder);

        schemaVersionLifecycleStateMachine = builder.build();
    }

    private CustomSchemaStateExecutor createSchemaReviewExecutor(Map<String, Object> props,
            SchemaVersionLifecycleStateMachine.Builder builder) {
        Map<String, Object> schemaReviewExecConfig = (Map<String, Object>) props
                .getOrDefault("customSchemaStateExecutor", Collections.emptyMap());
        String className = (String) schemaReviewExecConfig.getOrDefault("className",
                DEFAULT_SCHEMA_REVIEW_EXECUTOR_CLASS);
        Map<String, ?> executorProps = (Map<String, ?>) schemaReviewExecConfig.getOrDefault("props",
                Collections.emptyMap());
        CustomSchemaStateExecutor customSchemaStateExecutor;
        try {
            customSchemaStateExecutor = (CustomSchemaStateExecutor) Class
                    .forName(className, true, Thread.currentThread().getContextClassLoader()).newInstance();
        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
            LOG.error("Error encountered while loading class [{}]", className, e);
            throw new IllegalArgumentException(e);
        }

        customSchemaStateExecutor.init(builder, SchemaVersionLifecycleStates.REVIEWED.getId(),
                SchemaVersionLifecycleStates.CHANGES_REQUIRED.getId(), executorProps);

        return customSchemaStateExecutor;
    }

    public SchemaVersionLifecycleStateMachine getSchemaVersionLifecycleStateMachine() {
        return schemaVersionLifecycleStateMachine;
    }

    public SchemaVersionRetriever getSchemaVersionRetriever() {
        return schemaVersionRetriever;
    }

    public SchemaIdVersion addSchemaVersion(String schemaBranchName, SchemaMetadata schemaMetadata,
            SchemaVersion schemaVersion, Function<SchemaMetadata, Long> registerSchemaMetadataFn)
            throws IncompatibleSchemaException, InvalidSchemaException, SchemaNotFoundException,
            SchemaBranchNotFoundException {

        Preconditions.checkNotNull(schemaBranchName, "Schema branch name can't be null");
        Preconditions.checkNotNull(schemaMetadata, "schemaMetadata can't be null");
        Preconditions.checkNotNull(schemaVersion, "schemaVersion can't be null");

        checkSchemaText(schemaVersion.getSchemaText());

        SchemaVersionInfo schemaVersionInfo;
        String schemaName = schemaMetadata.getName();
        // check whether there exists schema-metadata for schema-metadata-key
        SchemaMetadataInfo retrievedschemaMetadataInfo = getSchemaMetadataInfo(schemaName);
        Long schemaMetadataId;
        if (retrievedschemaMetadataInfo != null) {
            schemaMetadataId = retrievedschemaMetadataInfo.getId();
            // check whether the same schema text exists
            schemaVersionInfo = getSchemaVersionInfo(schemaName, schemaVersion.getSchemaText());
            if (schemaVersionInfo == null) {
                schemaVersionInfo = createSchemaVersion(schemaBranchName, schemaMetadata,
                        retrievedschemaMetadataInfo.getId(), schemaVersion);

            }
        } else {
            schemaMetadataId = registerSchemaMetadataFn.apply(schemaMetadata);
            schemaVersionInfo = createSchemaVersion(schemaBranchName, schemaMetadata, schemaMetadataId,
                    schemaVersion);
        }

        return new SchemaIdVersion(schemaMetadataId, schemaVersionInfo.getVersion(), schemaVersionInfo.getId());
    }

    private void checkSchemaText(String schemaText) throws InvalidSchemaException {
        if (schemaText == null || schemaText.trim().isEmpty()) {
            throw new InvalidSchemaException();
        }
    }

    public SchemaIdVersion addSchemaVersion(String schemaBranchName, String schemaName, SchemaVersion schemaVersion)
            throws SchemaNotFoundException, IncompatibleSchemaException, InvalidSchemaException,
            SchemaBranchNotFoundException {

        Preconditions.checkNotNull(schemaBranchName, "Schema branch name can't be null");
        Preconditions.checkNotNull(schemaName, "schemaName can't be null");
        Preconditions.checkNotNull(schemaVersion, "schemaVersion can't be null");

        checkSchemaText(schemaVersion.getSchemaText());

        // check whether there exists schema-metadata for schema-metadata-key
        SchemaMetadataInfo schemaMetadataInfo = getSchemaMetadataInfo(schemaName);
        if (schemaMetadataInfo != null) {
            return addSchemaVersion(schemaBranchName, schemaMetadataInfo, schemaVersion);
        } else {
            throw new SchemaNotFoundException("SchemaMetadata not found with the schemaName: " + schemaName);
        }
    }

    public SchemaIdVersion addSchemaVersion(String schemaBranchName, SchemaMetadataInfo schemaMetadataInfo,
            SchemaVersion schemaVersion) throws SchemaNotFoundException, IncompatibleSchemaException,
            InvalidSchemaException, SchemaBranchNotFoundException {

        Preconditions.checkNotNull(schemaBranchName, "Schema branch name can't be null");
        checkSchemaText(schemaVersion.getSchemaText());

        SchemaVersionInfo schemaVersionInfo;
        // check whether there exists schema-metadata for schema-metadata-key
        SchemaMetadata schemaMetadata = schemaMetadataInfo.getSchemaMetadata();
        // check whether the same schema text exists
        schemaVersionInfo = findSchemaVersion(schemaBranchName, schemaMetadata.getType(),
                schemaVersion.getSchemaText(), schemaMetadataInfo.getSchemaMetadata().getName());
        if (schemaVersionInfo == null) {
            schemaVersionInfo = createSchemaVersion(schemaBranchName, schemaMetadata, schemaMetadataInfo.getId(),
                    schemaVersion);
        }

        return new SchemaIdVersion(schemaMetadataInfo.getId(), schemaVersionInfo.getVersion(),
                schemaVersionInfo.getId());
    }

    public SchemaVersionInfo getLatestEnabledSchemaVersionInfo(String schemaBranchName, String schemaName)
            throws SchemaNotFoundException, SchemaBranchNotFoundException {

        Preconditions.checkNotNull(schemaBranchName, "Schema branch name can't be null");
        Preconditions.checkNotNull(schemaName, "schemaName can't be null");

        return getLatestSchemaVersionInfo(schemaBranchName, schemaName,
                SchemaVersionLifecycleStates.ENABLED.getId());
    }

    public SchemaVersionInfo getLatestSchemaVersionInfo(String schemaBranchName, String schemaName)
            throws SchemaNotFoundException, SchemaBranchNotFoundException {

        Preconditions.checkNotNull(schemaBranchName, "Schema branch name can't be null");
        Preconditions.checkNotNull(schemaName, "schemaName can't be null");

        return getLatestSchemaVersionInfo(schemaBranchName, schemaName, null);
    }

    public SchemaVersionInfo getLatestSchemaVersionInfo(String schemaBranchName, String schemaName, Byte stateId)
            throws SchemaNotFoundException, SchemaBranchNotFoundException {

        Preconditions.checkNotNull(schemaBranchName, "Schema branch name can't be null");
        Preconditions.checkNotNull(schemaName, "schemaName can't be null");

        Collection<SchemaVersionInfo> schemaVersionInfos = getAllVersions(schemaBranchName, schemaName);

        SchemaVersionInfo latestSchema = null;
        if (schemaVersionInfos != null && !schemaVersionInfos.isEmpty()) {
            for (SchemaVersionInfo schemaVersionInfo : schemaVersionInfos) {
                if (stateId == null || schemaVersionInfo.getStateId().equals(stateId)) {
                    latestSchema = schemaVersionInfo;
                    break;
                }
            }
        }

        return latestSchema;
    }

    public SchemaVersionInfo getLatestSchemaVersionInfo(String schemaName) throws SchemaNotFoundException {
        Preconditions.checkNotNull(schemaName, "schemaName can't be null");

        return getLatestSchemaVersionInfo(schemaName, (Byte) null);
    }

    public SchemaVersionInfo getLatestSchemaVersionInfo(String schemaName, Byte stateId)
            throws SchemaNotFoundException {
        Preconditions.checkNotNull(schemaName, "schemaName can't be null");

        Collection<SchemaVersionInfo> schemaVersionInfos = getAllVersions(schemaName);

        SchemaVersionInfo latestSchema = null;
        if (schemaVersionInfos != null && !schemaVersionInfos.isEmpty()) {
            for (SchemaVersionInfo schemaVersionInfo : schemaVersionInfos) {
                if (stateId == null || schemaVersionInfo.getStateId().equals(stateId)) {
                    latestSchema = schemaVersionInfo;
                    break;
                }
            }
        }

        return latestSchema;
    }

    private SchemaVersionInfo createSchemaVersion(String schemaBranchName, SchemaMetadata schemaMetadata,
            Long schemaMetadataId, SchemaVersion schemaVersion) throws IncompatibleSchemaException,
            InvalidSchemaException, SchemaNotFoundException, SchemaBranchNotFoundException {

        Preconditions.checkNotNull(schemaBranchName, "schemaBranchName must not be null");
        Preconditions.checkNotNull(schemaMetadataId, "schemaMetadataId must not be null");

        String type = schemaMetadata.getType();
        if (getSchemaProvider(type) == null) {
            throw new UnsupportedSchemaTypeException("Given schema type " + type + " not supported");
        }

        SchemaBranch schemaBranch = null;
        try {
            schemaBranch = schemaBranchCache
                    .get(SchemaBranchCache.Key.of(new SchemaBranchKey(schemaBranchName, schemaMetadata.getName())));
        } catch (SchemaBranchNotFoundException e) {
            // Ignore this error
        }

        if (schemaBranch == null) {
            if (getAllVersions(schemaBranchName, schemaMetadata.getName()).size() != 0)
                throw new RuntimeException(String.format(
                        "Schema name : '%s' and branch name : '%s' has schema version, yet failed to obtain schema branch instance",
                        schemaMetadata.getName(), schemaBranchName));

        }

        // generate fingerprint, it parses the schema and checks for semantic validation.
        // throws InvalidSchemaException for invalid schemas.
        final String fingerprint = getFingerprint(type, schemaVersion.getSchemaText());
        final String schemaName = schemaMetadata.getName();

        SchemaVersionStorable schemaVersionStorable = new SchemaVersionStorable();
        final Long schemaVersionStorableId = storageManager.nextId(schemaVersionStorable.getNameSpace());
        schemaVersionStorable.setId(schemaVersionStorableId);
        schemaVersionStorable.setSchemaMetadataId(schemaMetadataId);

        schemaVersionStorable.setFingerprint(fingerprint);

        schemaVersionStorable.setName(schemaName);

        schemaVersionStorable.setSchemaText(schemaVersion.getSchemaText());
        schemaVersionStorable.setDescription(schemaVersion.getDescription());
        schemaVersionStorable.setTimestamp(System.currentTimeMillis());

        schemaVersionStorable.setState(DEFAULT_VERSION_STATE.getId());

        if (!schemaBranchName.equals(SchemaBranch.MASTER_BRANCH)) {
            schemaVersion.setState(SchemaVersionLifecycleStates.INITIATED.getId());
            schemaVersion.setStateDetails(null);
        }

        // take a lock for a schema with same name.
        int retryCt = 0;
        while (true) {
            try {
                Integer version = 0;
                Byte initialState = schemaVersion.getInitialState();
                if (schemaMetadata.isEvolve()) {
                    // if the given version is added with enabled or initiated state then only check for compatibility
                    if (SchemaVersionLifecycleStates.ENABLED.getId().equals(initialState)
                            || SchemaVersionLifecycleStates.INITIATED.getId().equals(initialState)) {
                        CompatibilityResult compatibilityResult = checkCompatibility(schemaBranchName, schemaName,
                                schemaVersion.getSchemaText());
                        if (!compatibilityResult.isCompatible()) {
                            String errMsg = String.format(
                                    "Given schema is not compatible with latest schema versions. \n"
                                            + "Error location: [%s] \n" + "Error encountered is: [%s]",
                                    compatibilityResult.getErrorLocation(), compatibilityResult.getErrorMessage());
                            LOG.error(errMsg);
                            throw new IncompatibleSchemaException(errMsg);
                        }
                    }
                    SchemaVersionInfo latestSchemaVersionInfo = getLatestSchemaVersionInfo(schemaName);
                    if (latestSchemaVersionInfo != null) {
                        version = latestSchemaVersionInfo.getVersion();
                    }
                }
                schemaVersionStorable.setVersion(version + 1);

                storageManager.add(schemaVersionStorable);
                updateSchemaVersionState(schemaVersionStorable.getId(), 1, initialState,
                        schemaVersion.getStateDetails());

                break;
            } catch (StorageException e) {
                // optimistic to try the next try would be successful. When retry attempts are exhausted, throw error back to invoker.
                if (++retryCt == DEFAULT_RETRY_CT) {
                    LOG.error(
                            "Giving up after retry attempts [{}] while trying to add new version of schema with metadata [{}]",
                            retryCt, schemaMetadata, e);
                    throw e;
                }
                LOG.debug(
                        "Encountered storage exception while trying to add a new version, attempting again : [{}] with error: [{}]",
                        retryCt, e);
            }
        }

        // fetching this as the ID may have been set by storage manager.
        Long schemaInstanceId = schemaVersionStorable.getId();

        SchemaBranchVersionMapping schemaBranchVersionMapping = new SchemaBranchVersionMapping(schemaBranch.getId(),
                schemaInstanceId);
        storageManager.add(schemaBranchVersionMapping);

        String storableNamespace = new SchemaFieldInfoStorable().getNameSpace();
        List<SchemaFieldInfo> schemaFieldInfos = getSchemaProvider(type)
                .generateFields(schemaVersionStorable.getSchemaText());
        for (SchemaFieldInfo schemaFieldInfo : schemaFieldInfos) {
            final Long fieldInstanceId = storageManager.nextId(storableNamespace);
            SchemaFieldInfoStorable schemaFieldInfoStorable = SchemaFieldInfoStorable
                    .fromSchemaFieldInfo(schemaFieldInfo, fieldInstanceId);
            schemaFieldInfoStorable.setSchemaInstanceId(schemaInstanceId);
            schemaFieldInfoStorable.setTimestamp(System.currentTimeMillis());
            storageManager.add(schemaFieldInfoStorable);
        }

        return schemaVersionStorable.toSchemaVersionInfo();
    }

    private void updateSchemaVersionState(Long schemaVersionId, Integer sequence, Byte initialState,
            byte[] stateDetails) throws SchemaNotFoundException {
        try {
            SchemaVersionLifecycleContext schemaVersionLifecycleContext = new SchemaVersionLifecycleContext(
                    schemaVersionId, sequence, createSchemaVersionService(), schemaVersionLifecycleStateMachine,
                    customSchemaStateExecutor);
            schemaVersionLifecycleContext
                    .setState(schemaVersionLifecycleStateMachine.getStates().get(initialState));
            schemaVersionLifecycleContext.setDetails(stateDetails);
            schemaVersionLifecycleContext.updateSchemaVersionState();
        } catch (SchemaLifecycleException e) {
            throw new RuntimeException(e);
        }
    }

    private SchemaProvider getSchemaProvider(String type) {
        return schemaMetadataFetcher.getSchemaProvider(type);
    }

    public CompatibilityResult checkCompatibility(String schemaBranchName, String schemaName, String toSchema)
            throws SchemaNotFoundException, SchemaBranchNotFoundException {

        Preconditions.checkNotNull(schemaBranchName, "Schema branch name can't be null");

        SchemaMetadataInfo schemaMetadataInfo = getSchemaMetadataInfo(schemaName);
        SchemaMetadata schemaMetadata = schemaMetadataInfo.getSchemaMetadata();
        SchemaValidationLevel validationLevel = schemaMetadata.getValidationLevel();
        CompatibilityResult compatibilityResult = null;
        switch (validationLevel) {
        case LATEST:
            SchemaVersionInfo latestSchemaVersionInfo = getLatestEnabledSchemaVersionInfo(schemaBranchName,
                    schemaName);
            if (latestSchemaVersionInfo != null) {
                compatibilityResult = checkCompatibility(schemaMetadata.getType(), toSchema,
                        latestSchemaVersionInfo.getSchemaText(), schemaMetadata.getCompatibility());
                if (!compatibilityResult.isCompatible()) {
                    LOG.info(
                            "Received schema is not compatible with the latest schema versions [{}] with schema name [{}]",
                            latestSchemaVersionInfo.getVersion(), schemaName);
                }
            }
            break;
        case ALL:
            Collection<SchemaVersionInfo> schemaVersionInfos = getAllVersions(schemaBranchName, schemaName);
            for (SchemaVersionInfo schemaVersionInfo : schemaVersionInfos) {
                if (SchemaVersionLifecycleStates.ENABLED.getId().equals(schemaVersionInfo.getStateId())) {
                    compatibilityResult = checkCompatibility(schemaMetadata.getType(), toSchema,
                            schemaVersionInfo.getSchemaText(), schemaMetadata.getCompatibility());
                    if (!compatibilityResult.isCompatible()) {
                        LOG.info(
                                "Received schema is not compatible with one of the schema versions [{}] with schema name [{}]",
                                schemaVersionInfo.getVersion(), schemaName);
                        break;
                    }
                }
            }
            break;
        }
        return compatibilityResult != null ? compatibilityResult
                : CompatibilityResult.createCompatibleResult(toSchema);
    }

    private CompatibilityResult checkCompatibility(String type, String toSchema, String existingSchema,
            SchemaCompatibility compatibility) {
        SchemaProvider schemaProvider = getSchemaProvider(type);
        if (schemaProvider == null) {
            throw new IllegalStateException("No SchemaProvider registered for type: " + type);
        }

        return schemaProvider.checkCompatibility(toSchema, existingSchema, compatibility);
    }

    public Collection<SchemaVersionInfo> getAllVersions(final String schemaBranchName, final String schemaName)
            throws SchemaNotFoundException, SchemaBranchNotFoundException {

        Preconditions.checkNotNull(schemaBranchName, "Schema branch name can't be null");

        Collection<SchemaVersionInfo> schemaVersionInfos;
        SchemaBranchKey schemaBranchKey = new SchemaBranchKey(schemaBranchName, schemaName);

        schemaVersionInfos = Lists
                .reverse(getSortedSchemaVersions(schemaBranchCache.get(SchemaBranchCache.Key.of(schemaBranchKey))));
        if (schemaVersionInfos == null || schemaVersionInfos.isEmpty())
            schemaVersionInfos = Collections.emptyList();

        return schemaVersionInfos;
    }

    public Collection<SchemaVersionInfo> getAllVersions(final String schemaBranchName, final String schemaName,
            final List<Byte> stateIds) throws SchemaNotFoundException, SchemaBranchNotFoundException {

        Preconditions.checkNotNull(schemaBranchName, "Schema branch name can't be null");
        Preconditions.checkNotNull(stateIds, "State Ids can't be null");

        Set<Byte> stateIdSet = stateIds.stream().collect(Collectors.toSet());

        return getAllVersions(schemaBranchName, schemaName).stream()
                .filter(schemaVersionInfo -> stateIdSet.contains(schemaVersionInfo.getStateId()))
                .collect(Collectors.toList());
    }

    public Collection<SchemaVersionInfo> getAllVersions(final String schemaName) throws SchemaNotFoundException {
        List<QueryParam> queryParams = Collections
                .singletonList(new QueryParam(SchemaVersionStorable.NAME, schemaName));

        SchemaMetadataInfo schemaMetadataInfo = getSchemaMetadataInfo(schemaName);
        if (schemaMetadataInfo == null) {
            throw new SchemaNotFoundException("Schema not found with name " + schemaName);
        }

        Collection<SchemaVersionStorable> storables = storageManager.find(SchemaVersionStorable.NAME_SPACE,
                queryParams, Collections.singletonList(OrderByField.of(SchemaVersionStorable.VERSION, true)));
        List<SchemaVersionInfo> schemaVersionInfos;
        if (storables != null && !storables.isEmpty()) {
            schemaVersionInfos = storables.stream().map(SchemaVersionStorable::toSchemaVersionInfo)
                    .collect(Collectors.toList());
        } else {
            schemaVersionInfos = Collections.emptyList();
        }
        return schemaVersionInfos;
    }

    private SchemaMetadataInfo getSchemaMetadataInfo(String schemaName) {
        return schemaMetadataFetcher.getSchemaMetadataInfo(schemaName);
    }

    public SchemaVersionInfo getSchemaVersionInfo(String schemaName, String schemaText)
            throws SchemaNotFoundException, InvalidSchemaException, SchemaBranchNotFoundException {
        SchemaMetadataInfo schemaMetadataInfo = getSchemaMetadataInfo(schemaName);
        if (schemaMetadataInfo == null) {
            throw new SchemaNotFoundException("No schema found for schema metadata key: " + schemaName);
        }

        return findSchemaVersion(SchemaBranch.MASTER_BRANCH, schemaMetadataInfo.getSchemaMetadata().getType(),
                schemaText, schemaName);
    }

    private SchemaVersionInfo fetchSchemaVersionInfo(Long id) throws SchemaNotFoundException {
        StorableKey storableKey = new StorableKey(SchemaVersionStorable.NAME_SPACE,
                SchemaVersionStorable.getPrimaryKey(id));

        SchemaVersionStorable versionedSchema = storageManager.get(storableKey);
        if (versionedSchema == null) {
            throw new SchemaNotFoundException("No Schema version exists with id " + id);
        }
        return versionedSchema.toSchemaVersionInfo();
    }

    private SchemaVersionInfo findSchemaVersion(String schemaBranchName, String type, String schemaText,
            String schemaMetadataName)
            throws InvalidSchemaException, SchemaNotFoundException, SchemaBranchNotFoundException {

        Preconditions.checkNotNull(schemaBranchName, "Schema branch name can't be null");

        String fingerPrint = getFingerprint(type, schemaText);
        LOG.debug("Fingerprint of the given schema [{}] is [{}]", schemaText, fingerPrint);
        List<QueryParam> queryParams = Lists.newArrayList(
                new QueryParam(SchemaVersionStorable.NAME, schemaMetadataName),
                new QueryParam(SchemaVersionStorable.FINGERPRINT, fingerPrint));

        Collection<SchemaVersionStorable> versionedSchemas = storageManager.find(SchemaVersionStorable.NAME_SPACE,
                queryParams);

        Set<Long> matchedSchemaVersionIds = null;
        if (versionedSchemas != null && !versionedSchemas.isEmpty()) {
            if (versionedSchemas.size() > 1) {
                LOG.warn("Exists more than one schema with schemaMetadataName: [{}] and schemaText [{}]",
                        schemaMetadataName, schemaText);
            }

            matchedSchemaVersionIds = versionedSchemas.stream()
                    .map(schemaVersionStorable -> schemaVersionStorable.getId()).collect(Collectors.toSet());
        }

        if (matchedSchemaVersionIds == null) {
            return null;
        } else {
            SchemaBranch schemaBranch = schemaBranchCache
                    .get(SchemaBranchCache.Key.of(new SchemaBranchKey(schemaBranchName, schemaMetadataName)));

            for (SchemaVersionInfo schemaVersionInfo : getSortedSchemaVersions(schemaBranch)) {
                if (matchedSchemaVersionIds.contains(schemaVersionInfo.getId()))
                    return schemaVersionInfo;
            }

            return null;
        }
    }

    private String getFingerprint(String type, String schemaText)
            throws InvalidSchemaException, SchemaNotFoundException {
        SchemaProvider schemaProvider = getSchemaProvider(type);
        return Hex.encodeHexString(schemaProvider.getFingerprint(schemaText));
    }

    public SchemaVersionInfo getSchemaVersionInfo(SchemaIdVersion schemaIdVersion) throws SchemaNotFoundException {
        return schemaVersionInfoCache.getSchema(SchemaVersionInfoCache.Key.of(schemaIdVersion));
    }

    public SchemaVersionInfo getSchemaVersionInfo(SchemaVersionKey schemaVersionKey)
            throws SchemaNotFoundException {
        return schemaVersionInfoCache.getSchema(SchemaVersionInfoCache.Key.of(schemaVersionKey));
    }

    public void deleteSchemaVersion(SchemaVersionKey schemaVersionKey)
            throws SchemaNotFoundException, SchemaLifecycleException {
        SchemaVersionInfoCache.Key schemaVersionCacheKey = new SchemaVersionInfoCache.Key(schemaVersionKey);
        SchemaVersionInfo schemaVersionInfo = schemaVersionInfoCache.getSchema(schemaVersionCacheKey);
        invalidateSchemaInAllHAServer(schemaVersionCacheKey);
        storageManager.remove(createSchemaVersionStorableKey(schemaVersionInfo.getId()));
        deleteSchemaVersionBranchMapping(schemaVersionInfo.getId());
    }

    public SchemaVersionMergeResult mergeSchemaVersion(Long schemaVersionId,
            SchemaVersionMergeStrategy schemaVersionMergeStrategy)
            throws SchemaNotFoundException, IncompatibleSchemaException {

        try {
            SchemaVersionInfo schemaVersionInfo = getSchemaVersionInfo(new SchemaIdVersion(schemaVersionId));
            SchemaMetadataInfo schemaMetadataInfo = getSchemaMetadataInfo(schemaVersionInfo.getName());

            Set<SchemaBranch> schemaBranches = getSchemaBranches(schemaVersionId).stream().filter(schemaBranch -> {
                try {
                    return !getRootVersion(schemaBranch).getId().equals(schemaVersionId);
                } catch (SchemaNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }).collect(Collectors.toSet());

            if (schemaBranches.size() > 1) {
                throw new SchemaVersionMergeException(String.format(
                        "Can't determine a unique schema branch for schema version id : '%s'", schemaVersionId));
            } else if (schemaBranches.size() == 0) {
                throw new SchemaVersionMergeException(String
                        .format("Schema version id : '%s' is not associated with any branch", schemaVersionId));
            }

            Long schemaBranchId = schemaBranches.iterator().next().getId();
            SchemaBranch schemaBranch = schemaBranchCache.get(SchemaBranchCache.Key.of(schemaBranchId));

            if (schemaVersionMergeStrategy.equals(SchemaVersionMergeStrategy.PESSIMISTIC)) {
                SchemaVersionInfo latestSchemaVersion = getLatestEnabledSchemaVersionInfo(
                        SchemaBranch.MASTER_BRANCH, schemaMetadataInfo.getSchemaMetadata().getName());
                SchemaVersionInfo rootSchemaVersion = getRootVersion(schemaBranch);
                if (!latestSchemaVersion.getId().equals(rootSchemaVersion.getId())) {
                    throw new SchemaVersionMergeException(String.format(
                            "The latest version of '%s' is different from the root version of the branch : '%s'",
                            SchemaBranch.MASTER_BRANCH, schemaMetadataInfo.getSchemaMetadata().getName()));
                }
            }

            byte[] initializedStateDetails;
            try {
                initializedStateDetails = ObjectMapperUtils
                        .serialize(new InitializedStateDetails(schemaBranch.getName(), schemaVersionInfo.getId()));
            } catch (JsonProcessingException e) {
                throw new RuntimeException(String.format("Failed to serialize initializedState for %s and %s",
                        schemaBranch.getName(), schemaVersionInfo.getId()));
            }

            SchemaVersionInfo createdSchemaVersionInfo;
            try {
                SchemaVersionInfo existingSchemaVersionInfo = findSchemaVersion(SchemaBranch.MASTER_BRANCH,
                        schemaMetadataInfo.getSchemaMetadata().getType(), schemaVersionInfo.getSchemaText(),
                        schemaMetadataInfo.getSchemaMetadata().getName());
                if (existingSchemaVersionInfo != null) {
                    String mergeMessage = String.format(
                            "Given version %d is already merged to master with version %d", schemaVersionId,
                            existingSchemaVersionInfo.getVersion());
                    LOG.info(mergeMessage);
                    return new SchemaVersionMergeResult(new SchemaIdVersion(schemaMetadataInfo.getId(),
                            existingSchemaVersionInfo.getVersion(), existingSchemaVersionInfo.getId()),
                            mergeMessage);
                }

                createdSchemaVersionInfo = createSchemaVersion(SchemaBranch.MASTER_BRANCH,
                        schemaMetadataInfo.getSchemaMetadata(), schemaMetadataInfo.getId(),
                        new SchemaVersion(schemaVersionInfo.getSchemaText(), schemaVersionInfo.getDescription(),
                                SchemaVersionLifecycleStates.INITIATED.getId(), initializedStateDetails));
            } catch (InvalidSchemaException e) {
                throw new SchemaVersionMergeException(
                        String.format("Failed to merge schema version : '%s'", schemaVersionId.toString()), e);
            }

            Collection<SchemaVersionStateStorable> schemaVersionStates = storageManager.find(
                    SchemaVersionStateStorable.NAME_SPACE,
                    Collections.singletonList(new QueryParam(SchemaVersionStateStorable.SCHEMA_VERSION_ID,
                            schemaVersionId.toString())),
                    Collections.singletonList(OrderByField.of(SchemaVersionStateStorable.SEQUENCE, true)));

            if (schemaVersionStates == null || schemaVersionStates.isEmpty()) {
                throw new RuntimeException(String.format(
                        "The database doesn't have any state transition recorded for the schema version id : '%s'",
                        schemaVersionId));
            }

            updateSchemaVersionState(createdSchemaVersionInfo.getId(),
                    schemaVersionStates.iterator().next().getSequence(),
                    SchemaVersionLifecycleStates.ENABLED.getId(), null);

            String mergeMessage = String.format("Given version %d is merged successfully to master with version %d",
                    schemaVersionId, createdSchemaVersionInfo.getVersion());
            LOG.info(mergeMessage);
            return new SchemaVersionMergeResult(new SchemaIdVersion(schemaMetadataInfo.getId(),
                    createdSchemaVersionInfo.getVersion(), createdSchemaVersionInfo.getId()), mergeMessage);
        } catch (SchemaBranchNotFoundException e) {
            throw new SchemaVersionMergeException(
                    String.format("Failed to merge schema version : '%s'", schemaVersionId.toString()), e);
        }
    }

    private ImmutablePair<SchemaVersionLifecycleContext, SchemaVersionLifecycleState> createSchemaVersionLifeCycleContextAndState(
            Long schemaVersionId) throws SchemaNotFoundException {
        // get the current state from storage for the given versionID
        // we can use a query to get max value for the column for a given schema-version-id but StorageManager does not
        // have API to take custom queries.
        Collection<SchemaVersionStateStorable> schemaVersionStates = storageManager.find(
                SchemaVersionStateStorable.NAME_SPACE,
                Collections.singletonList(
                        new QueryParam(SchemaVersionStateStorable.SCHEMA_VERSION_ID, schemaVersionId.toString())),
                Collections.singletonList(OrderByField.of(SchemaVersionStateStorable.SEQUENCE, true)));
        if (schemaVersionStates.isEmpty()) {
            throw new SchemaNotFoundException("No schema versions found with id " + schemaVersionId);
        }
        SchemaVersionStateStorable stateStorable = schemaVersionStates.iterator().next();

        SchemaVersionLifecycleState schemaVersionLifecycleState = schemaVersionLifecycleStateMachine.getStates()
                .get(stateStorable.getStateId());
        SchemaVersionService schemaVersionService = createSchemaVersionService();
        SchemaVersionLifecycleContext context = new SchemaVersionLifecycleContext(
                stateStorable.getSchemaVersionId(), stateStorable.getSequence(), schemaVersionService,
                schemaVersionLifecycleStateMachine, customSchemaStateExecutor);
        return new ImmutablePair<>(context, schemaVersionLifecycleState);
    }

    public SchemaVersionLifecycleContext createSchemaVersionLifeCycleContext(Long schemaVersionId,
            SchemaVersionLifecycleState schemaVersionLifecycleState) throws SchemaNotFoundException {
        // get the current state from storage for the given versionID
        // we can use a query to get max value for the column for a given schema-version-id but StorageManager does not
        // have API to take custom queries.

        List<QueryParam> queryParams = new ArrayList<>();
        queryParams.add(new QueryParam(SchemaVersionStateStorable.SCHEMA_VERSION_ID, schemaVersionId.toString()));
        queryParams.add(
                new QueryParam(SchemaVersionStateStorable.STATE, schemaVersionLifecycleState.getId().toString()));

        Collection<SchemaVersionStateStorable> schemaVersionStates = storageManager.find(
                SchemaVersionStateStorable.NAME_SPACE, queryParams,
                Collections.singletonList(OrderByField.of(SchemaVersionStateStorable.SEQUENCE, true)));
        if (schemaVersionStates.isEmpty()) {
            throw new SchemaNotFoundException("No schema versions found with id " + schemaVersionId);
        }
        SchemaVersionStateStorable stateStorable = schemaVersionStates.iterator().next();

        SchemaVersionService schemaVersionService = createSchemaVersionService();
        SchemaVersionLifecycleContext context = new SchemaVersionLifecycleContext(
                stateStorable.getSchemaVersionId(), stateStorable.getSequence(), schemaVersionService,
                schemaVersionLifecycleStateMachine, customSchemaStateExecutor);
        context.setDetails(stateStorable.getDetails());
        return context;
    }

    private SchemaVersionService createSchemaVersionService() {
        return new SchemaVersionService() {

            public void updateSchemaVersionState(SchemaVersionLifecycleContext schemaVersionLifecycleContext)
                    throws SchemaNotFoundException {
                storeSchemaVersionState(schemaVersionLifecycleContext);
            }

            public void deleteSchemaVersion(Long schemaVersionId)
                    throws SchemaNotFoundException, SchemaLifecycleException {
                doDeleteSchemaVersion(schemaVersionId);
            }

            @Override
            public SchemaMetadataInfo getSchemaMetadata(long schemaVersionId) throws SchemaNotFoundException {
                SchemaVersionInfo schemaVersionInfo = getSchemaVersionInfo(schemaVersionId);
                return getSchemaMetadataInfo(schemaVersionInfo.getName());
            }

            @Override
            public SchemaVersionInfo getSchemaVersionInfo(long schemaVersionId) throws SchemaNotFoundException {
                return SchemaVersionLifecycleManager.this
                        .getSchemaVersionInfo(new SchemaIdVersion(schemaVersionId));
            }

            @Override
            public CompatibilityResult checkForCompatibility(SchemaMetadata schemaMetadata, String toSchemaText,
                    String existingSchemaText) {
                return checkCompatibility(schemaMetadata.getType(), toSchemaText, existingSchemaText,
                        schemaMetadata.getCompatibility());
            }

            @Override
            public Collection<SchemaVersionInfo> getAllSchemaVersions(String schemaBranchName, String schemaName)
                    throws SchemaNotFoundException, SchemaBranchNotFoundException {
                return getAllVersions(schemaBranchName, schemaName);
            }
        };
    }

    private void storeSchemaVersionState(SchemaVersionLifecycleContext schemaVersionLifecycleContext)
            throws SchemaNotFoundException {
        // store versions state, sequence
        SchemaVersionStateStorable stateStorable = new SchemaVersionStateStorable();
        Long schemaVersionId = schemaVersionLifecycleContext.getSchemaVersionId();
        byte stateId = schemaVersionLifecycleContext.getState().getId();

        stateStorable.setSchemaVersionId(schemaVersionId);
        stateStorable.setSequence(schemaVersionLifecycleContext.getSequence() + 1);
        stateStorable.setStateId(stateId);
        stateStorable.setTimestamp(System.currentTimeMillis());
        stateStorable.setDetails(schemaVersionLifecycleContext.getDetails());
        stateStorable.setId(storageManager.nextId(SchemaVersionStateStorable.NAME_SPACE));

        storageManager.add(stateStorable);

        // store latest state in versions entity
        StorableKey storableKey = new StorableKey(SchemaVersionStorable.NAME_SPACE,
                SchemaVersionStorable.getPrimaryKey(schemaVersionId));
        SchemaVersionStorable versionedSchema = storageManager.get(storableKey);
        if (versionedSchema == null) {
            throw new SchemaNotFoundException("No Schema version exists with id " + schemaVersionId);
        }
        versionedSchema.setState(stateId);
        storageManager.addOrUpdate(versionedSchema);

        // invalidate schema version from cache
        SchemaVersionInfoCache.Key schemaVersionCacheKey = SchemaVersionInfoCache.Key
                .of(new SchemaIdVersion(schemaVersionId));
        invalidateSchemaInAllHAServer(schemaVersionCacheKey);
    }

    public void enableSchemaVersion(Long schemaVersionId) throws SchemaNotFoundException, SchemaLifecycleException,
            IncompatibleSchemaException, SchemaBranchNotFoundException {
        ImmutablePair<SchemaVersionLifecycleContext, SchemaVersionLifecycleState> pair = createSchemaVersionLifeCycleContextAndState(
                schemaVersionId);
        ((InbuiltSchemaVersionLifecycleState) pair.getRight()).enable(pair.getLeft());
    }

    public void deleteSchemaVersion(Long schemaVersionId) throws SchemaNotFoundException, SchemaLifecycleException {
        ImmutablePair<SchemaVersionLifecycleContext, SchemaVersionLifecycleState> pair = createSchemaVersionLifeCycleContextAndState(
                schemaVersionId);
        ((InbuiltSchemaVersionLifecycleState) pair.getRight()).delete(pair.getLeft());
    }

    private void doDeleteSchemaVersion(Long schemaVersionId)
            throws SchemaNotFoundException, SchemaLifecycleException {
        SchemaVersionInfoCache.Key schemaVersionCacheKey = SchemaVersionInfoCache.Key
                .of(new SchemaIdVersion(schemaVersionId));
        invalidateSchemaInAllHAServer(schemaVersionCacheKey);
        storageManager.remove(createSchemaVersionStorableKey(schemaVersionId));
        deleteSchemaVersionBranchMapping(schemaVersionId);
    }

    private StorableKey createSchemaVersionStorableKey(Long id) {
        SchemaVersionStorable schemaVersionStorable = new SchemaVersionStorable();
        schemaVersionStorable.setId(id);
        return schemaVersionStorable.getStorableKey();
    }

    private void deleteSchemaVersionBranchMapping(Long schemaVersionId)
            throws SchemaNotFoundException, SchemaLifecycleException {
        List<QueryParam> schemaVersionMappingStorableQueryParams = Lists.newArrayList();
        schemaVersionMappingStorableQueryParams
                .add(new QueryParam(SchemaBranchVersionMapping.SCHEMA_VERSION_INFO_ID, schemaVersionId.toString()));
        List<OrderByField> orderByFields = new ArrayList<>();
        orderByFields.add(OrderByField.of(SchemaBranchVersionMapping.SCHEMA_VERSION_INFO_ID, false));

        Collection<SchemaBranchVersionMapping> storables = storageManager.find(SchemaBranchVersionMapping.NAMESPACE,
                schemaVersionMappingStorableQueryParams, orderByFields);

        if (storables == null || storables.isEmpty()) {
            LOG.debug("No need to delete schema version mapping as the database did a cascade delete");
            return;
        }

        if (storables.size() > 1) {
            List<String> branchNamesTiedToSchema = storables
                    .stream().map(storable -> schemaBranchCache
                            .get(SchemaBranchCache.Key.of(storable.getSchemaBranchId())).getName())
                    .collect(Collectors.toList());
            throw new SchemaLifecycleException(
                    String.format("Schema version with id : '%s' is tied with more than one branch : '%s' ",
                            schemaVersionId.toString(), Arrays.toString(branchNamesTiedToSchema.toArray())));
        }

        storageManager.remove(
                new StorableKey(SchemaBranchVersionMapping.NAMESPACE, storables.iterator().next().getPrimaryKey()));
    }

    public void archiveSchemaVersion(Long schemaVersionId)
            throws SchemaNotFoundException, SchemaLifecycleException {
        ImmutablePair<SchemaVersionLifecycleContext, SchemaVersionLifecycleState> pair = createSchemaVersionLifeCycleContextAndState(
                schemaVersionId);
        ((InbuiltSchemaVersionLifecycleState) pair.getRight()).archive(pair.getLeft());
    }

    public void disableSchemaVersion(Long schemaVersionId)
            throws SchemaNotFoundException, SchemaLifecycleException {
        ImmutablePair<SchemaVersionLifecycleContext, SchemaVersionLifecycleState> pair = createSchemaVersionLifeCycleContextAndState(
                schemaVersionId);
        ((InbuiltSchemaVersionLifecycleState) pair.getRight()).disable(pair.getLeft());
    }

    public void startSchemaVersionReview(Long schemaVersionId)
            throws SchemaNotFoundException, SchemaLifecycleException {
        ImmutablePair<SchemaVersionLifecycleContext, SchemaVersionLifecycleState> pair = createSchemaVersionLifeCycleContextAndState(
                schemaVersionId);
        ((InbuiltSchemaVersionLifecycleState) pair.getRight()).startReview(pair.getLeft());
    }

    public void executeState(Long schemaVersionId, Byte targetState, byte[] transitionDetails)
            throws SchemaLifecycleException, SchemaNotFoundException {
        ImmutablePair<SchemaVersionLifecycleContext, SchemaVersionLifecycleState> schemaLifeCycleContextAndState = createSchemaVersionLifeCycleContextAndState(
                schemaVersionId);
        SchemaVersionLifecycleContext schemaVersionLifecycleContext = schemaLifeCycleContextAndState.getLeft();
        SchemaVersionLifecycleState currentState = schemaLifeCycleContextAndState.getRight();

        schemaVersionLifecycleContext.setState(currentState);
        schemaVersionLifecycleContext.setDetails(transitionDetails);
        SchemaVersionLifecycleStateTransition transition = new SchemaVersionLifecycleStateTransition(
                currentState.getId(), targetState);
        SchemaVersionLifecycleStateAction action = schemaVersionLifecycleContext.getSchemaLifeCycleStatesMachine()
                .getTransitions().get(transition);
        try {
            List<SchemaVersionLifecycleStateTransitionListener> listeners = schemaVersionLifecycleContext
                    .getSchemaLifeCycleStatesMachine().getListeners().getOrDefault(transition, DEFAULT_LISTENERS);

            listeners.stream().forEach(listener -> listener.preStateTransition(schemaVersionLifecycleContext));
            action.execute(schemaVersionLifecycleContext);
            listeners.stream().forEach(listener -> listener.postStateTransition(schemaVersionLifecycleContext));
        } catch (SchemaLifecycleException e) {
            Throwable cause = e.getCause();
            if (cause != null && cause instanceof SchemaNotFoundException) {
                throw (SchemaNotFoundException) cause;
            }
            throw e;
        }
    }

    private SchemaVersionInfo retrieveSchemaVersionInfo(SchemaVersionKey schemaVersionKey)
            throws SchemaNotFoundException {
        String schemaName = schemaVersionKey.getSchemaName();
        Integer version = schemaVersionKey.getVersion();
        SchemaMetadataInfo schemaMetadataInfo = getSchemaMetadataInfo(schemaName);

        if (schemaMetadataInfo == null) {
            throw new SchemaNotFoundException("No SchemaMetadata exists with key: " + schemaName);
        }

        return fetchSchemaVersionInfo(schemaVersionKey.getSchemaName(), version);
    }

    private SchemaVersionInfo retrieveSchemaVersionInfo(SchemaIdVersion key) throws SchemaNotFoundException {
        SchemaVersionInfo schemaVersionInfo = null;
        if (key.getSchemaVersionId() != null) {
            schemaVersionInfo = fetchSchemaVersionInfo(key.getSchemaVersionId());
        } else if (key.getSchemaMetadataId() != null) {
            SchemaMetadataInfo schemaMetadataInfo = schemaMetadataFetcher
                    .getSchemaMetadataInfo(key.getSchemaMetadataId());
            Integer version = key.getVersion();
            schemaVersionInfo = fetchSchemaVersionInfo(schemaMetadataInfo.getSchemaMetadata().getName(), version);
        } else {
            throw new IllegalArgumentException("Invalid SchemaIdVersion: " + key);
        }

        return schemaVersionInfo;
    }

    private SchemaVersionRetriever createSchemaVersionRetriever() {
        return new SchemaVersionRetriever() {
            @Override
            public SchemaVersionInfo retrieveSchemaVersion(SchemaVersionKey key) throws SchemaNotFoundException {
                return retrieveSchemaVersionInfo(key);
            }

            @Override
            public SchemaVersionInfo retrieveSchemaVersion(SchemaIdVersion key) throws SchemaNotFoundException {
                return retrieveSchemaVersionInfo(key);
            }
        };
    }

    private SchemaVersionInfo fetchSchemaVersionInfo(String schemaName, Integer version)
            throws SchemaNotFoundException {
        LOG.info("##### fetching schema version for name: [{}] version: [{}]", schemaName, version);
        SchemaVersionInfo schemaVersionInfo = null;
        if (SchemaVersionKey.LATEST_VERSION.equals(version)) {
            schemaVersionInfo = getLatestSchemaVersionInfo(schemaName);
        } else {
            List<QueryParam> queryParams = Lists.newArrayList(
                    new QueryParam(SchemaVersionStorable.NAME, schemaName),
                    new QueryParam(SchemaVersionStorable.VERSION, version.toString()));

            Collection<SchemaVersionStorable> versionedSchemas = storageManager
                    .find(SchemaVersionStorable.NAME_SPACE, queryParams);
            if (versionedSchemas != null && !versionedSchemas.isEmpty()) {
                if (versionedSchemas.size() > 1) {
                    LOG.warn("More than one schema exists with name: [{}] and version [{}]", schemaName, version);
                }
                schemaVersionInfo = versionedSchemas.iterator().next().toSchemaVersionInfo();
            } else {
                throw new SchemaNotFoundException(
                        "No Schema version exists with name " + schemaName + " and version " + version);
            }
        }
        LOG.info("##### fetched schema version info [{}]", schemaVersionInfo);
        return schemaVersionInfo;
    }

    public Set<SchemaBranch> getSchemaBranches(Long schemaVersionId) throws SchemaBranchNotFoundException {
        List<QueryParam> schemaVersionMappingStorableQueryParams = new ArrayList<>();
        Set<SchemaBranch> schemaBranches = new HashSet<>();
        schemaVersionMappingStorableQueryParams
                .add(new QueryParam(SchemaBranchVersionMapping.SCHEMA_VERSION_INFO_ID, schemaVersionId.toString()));

        for (Storable storable : storageManager.find(SchemaBranchVersionMapping.NAMESPACE,
                schemaVersionMappingStorableQueryParams)) {
            schemaBranches.add(schemaBranchCache
                    .get(SchemaBranchCache.Key.of(((SchemaBranchVersionMapping) storable).getSchemaBranchId())));
        }

        return schemaBranches;
    }

    private List<SchemaVersionInfo> getSortedSchemaVersions(Long schemaBranchId)
            throws SchemaNotFoundException, SchemaBranchNotFoundException {
        List<QueryParam> schemaVersionMappingStorableQueryParams = Lists.newArrayList();
        schemaVersionMappingStorableQueryParams
                .add(new QueryParam(SchemaBranchVersionMapping.SCHEMA_BRANCH_ID, schemaBranchId.toString()));
        List<OrderByField> orderByFields = new ArrayList<>();
        orderByFields.add(OrderByField.of(SchemaBranchVersionMapping.SCHEMA_VERSION_INFO_ID, false));
        List<SchemaVersionInfo> schemaVersionInfos = new ArrayList<>();

        Collection<SchemaBranchVersionMapping> storables = storageManager.find(SchemaBranchVersionMapping.NAMESPACE,
                schemaVersionMappingStorableQueryParams, orderByFields);
        if (storables == null || storables.size() == 0) {
            if (schemaBranchCache.get(SchemaBranchCache.Key.of(schemaBranchId)).getName()
                    .equals(SchemaBranch.MASTER_BRANCH))
                return Collections.emptyList();
            else
                throw new InvalidSchemaBranchVersionMapping(String
                        .format("No schema versions are attached to the schema branch id : '%s'", schemaBranchId));
        }

        for (SchemaBranchVersionMapping storable : storables) {
            SchemaIdVersion schemaIdVersion = new SchemaIdVersion(storable.getSchemaVersionInfoId());
            schemaVersionInfos
                    .add(schemaVersionInfoCache.getSchema(SchemaVersionInfoCache.Key.of(schemaIdVersion)));
        }

        return schemaVersionInfos;
    }

    public List<SchemaVersionInfo> getSortedSchemaVersions(SchemaBranch schemaBranch)
            throws SchemaNotFoundException {
        try {
            return getSortedSchemaVersions(schemaBranch.getId());
        } catch (SchemaBranchNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public SchemaVersionInfo getRootVersion(SchemaBranch schemaBranch) throws SchemaNotFoundException {

        if (schemaBranch.getName().equals(SchemaBranch.MASTER_BRANCH)) {
            throw new SchemaNotFoundException(String.format(
                    "There is no root schema version attached to the schema branch '%s'", schemaBranch.getName()));
        }

        List<SchemaVersionInfo> sortedVersionInfo;
        try {
            sortedVersionInfo = getSortedSchemaVersions(schemaBranch.getId());
        } catch (SchemaBranchNotFoundException e) {
            throw new RuntimeException(e);
        }

        if (sortedVersionInfo == null)
            throw new SchemaNotFoundException(String.format(
                    "There were no schema versions attached to schema branch '%s'", schemaBranch.getName()));
        return sortedVersionInfo.iterator().next();
    }

    public void invalidateAllSchemaVersionCache() {
        schemaVersionInfoCache.invalidateAll();
    }

    public void invalidateSchemaVersionCache(SchemaVersionInfoCache.Key key) {
        schemaVersionInfoCache.invalidateSchema(key);
    }

    private void invalidateSchemaInAllHAServer(SchemaVersionInfoCache.Key key) {
        schemaVersionInfoCache.invalidateSchema(key);

        String keyAsString;

        try {
            keyAsString = ObjectMapperUtils.serializeToString(key);
        } catch (Exception e) {
            throw new RuntimeException(String.format("Failed to serialized key : %s", key), e);
        }

        haServerNotificationManager.notifyCacheInvalidation(schemaVersionInfoCache.getCacheType(), keyAsString);

    }

}