info.archinnov.achilles.internals.schema.SchemaValidator.java Source code

Java tutorial

Introduction

Here is the source code for info.archinnov.achilles.internals.schema.SchemaValidator.java

Source

/*
 * Copyright (C) 2012-2016 DuyHai DOAN
 *
 * 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 info.archinnov.achilles.internals.schema;

import static info.archinnov.achilles.annotations.SASI.Analyzer.NON_TOKENIZING_ANALYZER;
import static info.archinnov.achilles.annotations.SASI.Analyzer.STANDARD_ANALYZER;
import static info.archinnov.achilles.internals.metamodel.index.IndexType.*;
import static info.archinnov.achilles.validation.Validator.validateBeanMappingFalse;
import static info.archinnov.achilles.validation.Validator.validateBeanMappingTrue;
import static java.lang.String.format;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

import java.util.List;
import java.util.Optional;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.datastax.driver.core.*;

import info.archinnov.achilles.annotations.SASI;
import info.archinnov.achilles.internals.metamodel.AbstractProperty;
import info.archinnov.achilles.internals.metamodel.columns.ColumnType;
import info.archinnov.achilles.internals.metamodel.index.IndexImpl;
import info.archinnov.achilles.internals.metamodel.index.IndexInfo;
import info.archinnov.achilles.internals.parser.context.DSESearchInfoContext;
import info.archinnov.achilles.internals.parser.context.SASIInfoContext;

public class SchemaValidator {

    private static final Logger LOGGER = LoggerFactory.getLogger(SchemaValidator.class);

    public static void validateDefaultTTL(AbstractTableMetadata metadata, Optional<Integer> staticTTL,
            Class<?> entityClass) {
        if (staticTTL.isPresent()) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(format("Validating table %s default TTL value", metadata.getName()));
            }
            final int defaultTimeToLive = metadata.getOptions().getDefaultTimeToLive();
            validateBeanMappingTrue(staticTTL.get().equals(defaultTimeToLive),
                    "Default TTL '%s' declared on entity '%s' does not match detected default TTL '%s' in live schema",
                    staticTTL.get(), entityClass.getCanonicalName(), defaultTimeToLive);
        }
    }

    public static <T> void validateColumns(AbstractTableMetadata metadata,
            List<AbstractProperty<T, ?, ?>> properties, Class<T> entityClass) {

        for (AbstractProperty<T, ?, ?> x : properties) {
            final String cqlColumn = x.fieldInfo.quotedCqlColumn;
            final ColumnMetadata columnMeta = metadata.getColumn(cqlColumn);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(format("Validating column %s for table %s", cqlColumn, metadata.getName()));
            }

            validateBeanMappingTrue(columnMeta != null, "Cannot find column '%s' in live schema for entity '%s'",
                    cqlColumn, entityClass);

            final DataType runtimeType = columnMeta.getType();
            final DataType staticType = x.buildType();
            validateBeanMappingTrue(runtimeType.equals(staticType),
                    "Data type '%s' for column '%s' of entity '%s' does not match type in live schema '%s'",
                    staticType, cqlColumn, entityClass, runtimeType);

            if (x.fieldInfo.hasIndex()) {
                if (x.fieldInfo.indexInfo.impl == IndexImpl.NATIVE) {
                    final TableMetadata tableMetadata = (TableMetadata) metadata;
                    validateNativeIndex(entityClass, x, cqlColumn,
                            tableMetadata.getIndex(x.fieldInfo.indexInfo.name));
                } else if (x.fieldInfo.indexInfo.impl == IndexImpl.SASI) {
                    final TableMetadata tableMetadata = (TableMetadata) metadata;
                    validateSASIIndex(entityClass, x, cqlColumn,
                            tableMetadata.getIndex(x.fieldInfo.indexInfo.name));
                } else if (x.fieldInfo.indexInfo.impl == IndexImpl.DSE_SEARCH) {
                    final TableMetadata tableMetadata = (TableMetadata) metadata;
                    validateDSESearchIndex(entityClass, tableMetadata);
                }
            }

            if (x.fieldInfo.columnType == ColumnType.STATIC
                    || x.fieldInfo.columnType == ColumnType.STATIC_COUNTER) {
                validateBeanMappingTrue(columnMeta.isStatic(), "Column '%s' of entity '%s' should be static",
                        cqlColumn, entityClass);
            }
        }
    }

    private static void validateDSESearchIndex(Class<?> entityClass, TableMetadata tableMetadata) {
        final String tableName = tableMetadata.getName().toLowerCase();
        final String keyspaceName = tableMetadata.getKeyspace().getName().toLowerCase();
        final String indexName = keyspaceName + "_" + tableName + "_solr_query_index";
        final Optional<IndexMetadata> indexMeta = Optional.ofNullable(tableMetadata.getIndex(indexName));

        validateBeanMappingTrue(indexMeta.isPresent(), "Index name %s for entity '%s' cannot be found", indexName,
                entityClass);

        final IndexMetadata indexMetadata = indexMeta.get();
        final String indexClassName = indexMetadata.getIndexClassName();
        validateBeanMappingTrue(indexClassName.equals(DSESearchInfoContext.DSE_SEARCH_INDEX_CLASSNAME),
                "Index class name %s for entity '%s' should be %s", indexClassName, entityClass,
                DSESearchInfoContext.DSE_SEARCH_INDEX_CLASSNAME);
    }

    private static void validateSASIIndex(Class<?> entityClass, AbstractProperty<?, ?, ?> x, String cqlColumn,
            IndexMetadata indexMetadata) {
        final SASIInfoContext sasiInfo = x.fieldInfo.indexInfo.sasiInfoContext.get();
        final String indexName = sasiInfo.indexName;

        validateBeanMappingTrue(sasiInfo.indexMode.name().equals(indexMetadata.getOption("mode")),
                "Index name %s for column '%s' of entity '%s' should have option 'mode' = %s", indexName, cqlColumn,
                entityClass, indexMetadata.getOption("mode"));

        validateBeanMappingTrue(
                (sasiInfo.maxCompactionFlushMemoryInMb + "")
                        .equals(indexMetadata.getOption("max_compaction_flush_memory_in_mb")),
                "Index name %s for column '%s' of entity '%s' should have option 'max_compaction_flush_memory_in_mb' = %s",
                indexName, cqlColumn, entityClass, indexMetadata.getOption("max_compaction_flush_memory_in_mb"));

        if (sasiInfo.analyzed) {
            validateBeanMappingTrue(indexMetadata.getOption("analyzed").equals("true"),
                    "Index name %s for column '%s' of entity '%s' should have option 'analyzed' = true", indexName,
                    cqlColumn, entityClass);

            validateBeanMappingTrue(
                    sasiInfo.analyzerClass.analyzerClass().equals(indexMetadata.getOption("analyzer_class")),
                    "Index name %s for column '%s' of entity '%s' should have option 'analyzerClass' = %s",
                    indexName, cqlColumn, entityClass, indexMetadata.getOption("analyzerClass"));

            if (sasiInfo.analyzerClass == STANDARD_ANALYZER) {
                validateBeanMappingTrue(
                        sasiInfo.locale.trim().toLowerCase().equals(indexMetadata.getOption("tokenization_locale")),
                        "Index name %s for column '%s' of entity '%s' should have option 'tokenization_locale' = %s",
                        indexName, cqlColumn, entityClass, indexMetadata.getOption("tokenization_locale"));

                validateBeanMappingTrue(
                        (sasiInfo.enableStemming + "")
                                .equals(indexMetadata.getOption("tokenization_enable_stemming")),
                        "Index name %s for column '%s' of entity '%s' should have option 'tokenization_enable_stemming' = %s",
                        indexName, cqlColumn, entityClass, indexMetadata.getOption("tokenization_enable_stemming"));

                validateBeanMappingTrue(
                        (sasiInfo.skipStopWords + "")
                                .equals(indexMetadata.getOption("tokenization_skip_stop_words")),
                        "Index name %s for column '%s' of entity '%s' should have option 'tokenization_skip_stop_words' = %s",
                        indexName, cqlColumn, entityClass, indexMetadata.getOption("tokenization_skip_stop_words"));

                final String normalization = sasiInfo.normalization.forStandardAnalyzer();
                final String liveNormalization = indexMetadata.getOption(normalization);
                validateBeanMappingTrue(isNotBlank(liveNormalization) && liveNormalization.equals("true"),
                        "Index name %s for column '%s' of entity '%s' should have option '%s' = true", indexName,
                        cqlColumn, entityClass, normalization);
            } else if (sasiInfo.analyzerClass == NON_TOKENIZING_ANALYZER) {
                if (sasiInfo.normalization == SASI.Normalization.NONE) {
                    final String liveCasseSensitive = indexMetadata.getOption("case_sensitive");
                    validateBeanMappingTrue(isNotBlank(liveCasseSensitive) && liveCasseSensitive.equals("true"),
                            "Index name %s for column '%s' of entity '%s' should have option 'case_sensitive' = true",
                            indexName, cqlColumn, entityClass);
                } else {
                    final String normalization = sasiInfo.normalization.forNonTokenizingAnalyzer();
                    final String liveNormalization = indexMetadata.getOption(normalization);
                    validateBeanMappingTrue(isNotBlank(liveNormalization) && liveNormalization.equals("true"),
                            "Index name %s for column '%s' of entity '%s' should have option '%s' = true",
                            indexName, cqlColumn, entityClass, normalization);
                }
            }

        }
    }

    private static void validateNativeIndex(Class<?> entityClass, AbstractProperty<?, ?, ?> x, String cqlColumn,
            IndexMetadata indexMetadata) {
        final IndexInfo indexInfo = x.fieldInfo.indexInfo;

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(format("Validating index on column %s of table %s", cqlColumn,
                    indexMetadata.getTable().getName()));
        }

        final String indexName = indexInfo.name;
        validateBeanMappingTrue(indexName.equals(indexMetadata.getName()),
                "Index name '%s' for column '%s' of entity '%s' does not match name '%s' in live schema", indexName,
                cqlColumn, entityClass, indexMetadata.getName());

        final String indexTarget = indexMetadata.getTarget().toLowerCase();
        final boolean isIndexOnFullCollection = indexTarget.contains(format("full(%s)", cqlColumn));
        final boolean isIndexOnMapEntries = indexTarget.contains(format("entries(%s)", cqlColumn));
        final boolean isIndexOnMapKeys = indexTarget.contains(format("keys(%s)", cqlColumn));
        final boolean isCustomIndex = indexMetadata.isCustomIndex();

        switch (indexInfo.type) {
        case NORMAL:
            validateBeanMappingFalse(isIndexOnMapEntries,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", NORMAL,
                    cqlColumn, entityClass);
            validateBeanMappingFalse(isIndexOnFullCollection,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", NORMAL,
                    cqlColumn, entityClass);
            validateBeanMappingFalse(isIndexOnMapKeys,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", NORMAL,
                    cqlColumn, entityClass);
            validateBeanMappingFalse(isCustomIndex,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", NORMAL,
                    cqlColumn, entityClass);
            break;
        case COLLECTION:
            validateBeanMappingFalse(isIndexOnMapEntries,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", COLLECTION,
                    cqlColumn, entityClass);
            validateBeanMappingFalse(isIndexOnFullCollection,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", COLLECTION,
                    cqlColumn, entityClass);
            validateBeanMappingFalse(isIndexOnMapKeys,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", COLLECTION,
                    cqlColumn, entityClass);
            validateBeanMappingFalse(isCustomIndex,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", COLLECTION,
                    cqlColumn, entityClass);
            break;
        case FULL:
            validateBeanMappingTrue(isIndexOnFullCollection,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", FULL,
                    cqlColumn, entityClass);
            validateBeanMappingFalse(isIndexOnMapEntries,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", FULL,
                    cqlColumn, entityClass);
            validateBeanMappingFalse(isIndexOnMapKeys,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", FULL,
                    cqlColumn, entityClass);
            validateBeanMappingFalse(isCustomIndex,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", FULL,
                    cqlColumn, entityClass);
            break;
        case MAP_ENTRY:
            validateBeanMappingTrue(isIndexOnMapEntries,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", MAP_ENTRY,
                    cqlColumn, entityClass);
            validateBeanMappingFalse(isIndexOnFullCollection,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", MAP_ENTRY,
                    cqlColumn, entityClass);
            validateBeanMappingFalse(isIndexOnMapKeys,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", MAP_ENTRY,
                    cqlColumn, entityClass);
            validateBeanMappingFalse(isCustomIndex,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", MAP_ENTRY,
                    cqlColumn, entityClass);
            break;
        case MAP_KEY:
            validateBeanMappingFalse(isIndexOnMapEntries,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", MAP_KEY,
                    cqlColumn, entityClass);
            validateBeanMappingFalse(isIndexOnFullCollection,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", MAP_KEY,
                    cqlColumn, entityClass);
            validateBeanMappingTrue(isIndexOnMapKeys,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", MAP_KEY,
                    cqlColumn, entityClass);
            validateBeanMappingFalse(isCustomIndex,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", MAP_KEY,
                    cqlColumn, entityClass);
            break;
        case CUSTOM:
            validateBeanMappingFalse(isIndexOnMapEntries,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", CUSTOM,
                    cqlColumn, entityClass);
            validateBeanMappingFalse(isIndexOnFullCollection,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", CUSTOM,
                    cqlColumn, entityClass);
            validateBeanMappingFalse(isIndexOnMapKeys,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", CUSTOM,
                    cqlColumn, entityClass);
            validateBeanMappingTrue(isCustomIndex,
                    "Index type '%s' for column '%s' of entity '%s' does not match type in live schema", CUSTOM,
                    cqlColumn, entityClass);
        }
    }
}