com.stratio.cassandra.lucene.schema.Schema.java Source code

Java tutorial

Introduction

Here is the source code for com.stratio.cassandra.lucene.schema.Schema.java

Source

/*
 * Copyright 2014, Stratio.
 *
 * 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 com.stratio.cassandra.lucene.schema;

import com.google.common.base.Objects;
import com.stratio.cassandra.lucene.IndexException;
import com.stratio.cassandra.lucene.schema.analysis.ClasspathAnalyzerBuilder;
import com.stratio.cassandra.lucene.schema.analysis.PreBuiltAnalyzers;
import com.stratio.cassandra.lucene.schema.column.Columns;
import com.stratio.cassandra.lucene.schema.mapping.Mapper;
import com.stratio.cassandra.lucene.util.Log;
import com.stratio.cassandra.lucene.util.TokenLengthAnalyzer;
import org.apache.cassandra.config.CFMetaData;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.miscellaneous.PerFieldAnalyzerWrapper;
import org.apache.lucene.document.Document;

import java.io.Closeable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * The user-defined mapping from Cassandra columns to Lucene documents.
 *
 * @author Andres de la Pena {@literal <adelapena@stratio.com>}
 */
public class Schema implements Closeable {

    /** The {@link Columns} {@link Mapper}s. */
    private final Map<String, Mapper> mappers;

    /** The per field {@link Analyzer}s. */
    private final Map<String, Analyzer> analyzers;

    /** The default {@link Analyzer}. */
    private final Analyzer defaultAnalyzer;

    /** The wrapping all-in-one {@link Analyzer}. */
    private final Analyzer perFieldAnalyzer;

    /** The names of the mapped columns. */
    private final Set<String> mappedColumns;

    /**
     * Returns a new {@code Schema} for the specified {@link Mapper}s and {@link Analyzer}s.
     *
     * @param defaultAnalyzer The default {@link Analyzer} to be used.
     * @param mappers         The per field {@link Mapper}s builders to be used.
     * @param analyzers       The per field {@link Analyzer}s to be used.
     */
    public Schema(Analyzer defaultAnalyzer, Map<String, Mapper> mappers, Map<String, Analyzer> analyzers) {

        this.defaultAnalyzer = defaultAnalyzer != null ? defaultAnalyzer : PreBuiltAnalyzers.DEFAULT.get();
        this.mappers = mappers != null ? mappers : new HashMap<String, Mapper>();
        this.analyzers = analyzers != null ? analyzers : new HashMap<String, Analyzer>();
        mappedColumns = new HashSet<>();

        Map<String, Analyzer> perFieldAnalyzers = new HashMap<>();
        for (Map.Entry<String, Mapper> entry : this.mappers.entrySet()) {
            String name = entry.getKey();
            Mapper mapper = entry.getValue();
            String analyzerName = mapper.getAnalyzer();
            Analyzer analyzer;
            if (analyzerName != null) {
                analyzer = getAnalyzer(analyzerName);
                analyzer = new TokenLengthAnalyzer(analyzer);
                perFieldAnalyzers.put(name, analyzer);
            }
            mappedColumns.addAll(mapper.getMappedColumns());
        }
        Analyzer filteredDefaultAnalyzer = new TokenLengthAnalyzer(this.defaultAnalyzer);
        this.perFieldAnalyzer = new PerFieldAnalyzerWrapper(filteredDefaultAnalyzer, perFieldAnalyzers);
    }

    /**
     * Returns the default {@link Analyzer}.
     *
     * @return The default {@link Analyzer}.
     */
    public Analyzer getDefaultAnalyzer() {
        return defaultAnalyzer;
    }

    /**
     * Returns the {@link Analyzer} identified by the specified name. If there is no analyzer with the specified name,
     * then it will be interpreted as a class name and it will be instantiated by reflection.
     *
     * {@link IllegalArgumentException} is thrown if there is no {@link Analyzer} with such name.
     *
     * @param name The name of the {@link Analyzer} to be returned.
     * @return The {@link Analyzer} identified by the specified name.
     */
    public Analyzer getAnalyzer(String name) {
        if (StringUtils.isBlank(name)) {
            throw new IllegalArgumentException("Not null nor empty analyzer name required");
        }
        Analyzer analyzer = analyzers.get(name);
        if (analyzer == null) {
            analyzer = PreBuiltAnalyzers.get(name);
            if (analyzer == null) {
                try {
                    analyzer = (new ClasspathAnalyzerBuilder(name)).analyzer();
                } catch (Exception e) {
                    throw new IllegalArgumentException("Not found analyzer: " + name);
                }
            }
            analyzers.put(name, analyzer);
        }
        return analyzer;
    }

    /**
     * Returns the used filtered per field {@link Analyzer}.
     *
     * @return The used filtered per field {@link Analyzer}.
     */
    public Analyzer getAnalyzer() {
        return perFieldAnalyzer;
    }

    /**
     * Returns the {@link Mapper} identified by the specified field name, or {@code null} if not found.
     *
     * @param field A field name.
     * @return The {@link Mapper} identified by the specified field name, or {@code null} if not found.
     */
    public Mapper getMapper(String field) {
        String[] components = field.split("\\.");
        for (int i = components.length - 1; i >= 0; i--) {
            StringBuilder sb = new StringBuilder();
            for (int j = 0; j <= i; j++) {
                sb.append(components[j]);
                if (j < i) {
                    sb.append('.');
                }
            }
            Mapper mapper = mappers.get(sb.toString());
            if (mapper != null) {
                return mapper;
            }
        }
        return null;
    }

    /**
     * Validates the specified {@link Columns} for mapping.
     *
     * @param columns The {@link Columns} to be validated.
     */
    public void validate(Columns columns) {
        Document document = new Document();
        for (Mapper mapper : mappers.values()) {
            mapper.addFields(document, columns);
        }
    }

    /**
     * Adds to the specified {@link Document} the Lucene fields representing the specified {@link Columns}.
     *
     * This is done in a best-effort way, so each mapper errors are logged and ignored.
     *
     * @param document The Lucene {@link Document} where the fields are going to be added.
     * @param columns  The {@link Columns} to be added.
     */
    public void addFields(Document document, Columns columns) {
        for (Mapper mapper : mappers.values()) {
            try {
                mapper.addFields(document, columns);
            } catch (IndexException e) {
                Log.error(
                        "Error in Lucene index:\n\twhile mapping : %s\n\twith mapper   : %s\n\tcaused by     : %s",
                        columns, mapper, e.getMessage());
            }
        }
    }

    /**
     * Checks if this is consistent with the specified column family metadata.
     *
     * @param metadata A column family metadata.
     */
    public void validate(CFMetaData metadata) {
        for (Mapper mapper : mappers.values()) {
            mapper.validate(metadata);
        }
    }

    /**
     * Returns if there is any mapper mapping the specified column.
     *
     * @param column A column name.
     * @return {@code true} if there is any mapper mapping the specified column, {@code false} otherwise.
     */
    public boolean maps(String column) {
        return mappedColumns.contains(column);
    }

    /**
     * Returns if the specified {@link Columns} contains the all the mapped columns.
     *
     * @param columns A {@link Columns}.
     * @return {@code true} if the specified {@link Columns} contains the mapped columns, {@code false} otherwise.
     */
    public boolean mapsAll(Columns columns) {
        for (Mapper mapper : mappers.values()) {
            if (!mapper.maps(columns)) {
                return false;
            }
        }
        return true;
    }

    /** {@inheritDoc} */
    @Override
    public void close() {
        perFieldAnalyzer.close();
    }

    /** {@inheritDoc} */
    @Override
    public String toString() {
        return Objects.toStringHelper(this).add("mappers", mappers).add("analyzers", analyzers)
                .add("defaultAnalyzer", defaultAnalyzer).add("perFieldAnalyzer", perFieldAnalyzer).toString();
    }
}