com.qwazr.search.annotations.AnnotatedIndexService.java Source code

Java tutorial

Introduction

Here is the source code for com.qwazr.search.annotations.AnnotatedIndexService.java

Source

/**
 * Copyright 2016 Emmanuel Keller / QWAZR
 * <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.qwazr.search.annotations;

import com.qwazr.search.field.FieldDefinition;
import com.qwazr.search.index.*;
import com.qwazr.utils.ArrayUtils;
import com.qwazr.utils.IOUtils;
import com.qwazr.utils.StringUtils;
import com.qwazr.utils.server.ServerException;
import org.apache.http.HttpEntity;
import org.apache.http.util.EntityUtils;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.BiConsumer;

public class AnnotatedIndexService<T> {

    protected final AnnotatedServiceInterface annotatedService;

    protected final IndexServiceInterface indexService;

    private final Class<T> indexDefinitionClass;

    protected final String schemaName;

    protected final String indexName;

    protected final String similarityClass;

    private final Map<String, IndexField> indexFieldMap;

    private final Map<String, Field> fieldMap;

    /**
     * Create a new index service. A class with Index and IndexField annotations.
     *
     * @param indexService         the IndexServiceInterface to use
     * @param indexDefinitionClass an annotated class
     */
    public AnnotatedIndexService(IndexServiceInterface indexService, Class<T> indexDefinitionClass) {
        Objects.requireNonNull(indexService, "The indexService parameter is null");
        Objects.requireNonNull(indexDefinitionClass, "The indexDefinition parameter is null");
        this.indexService = indexService;
        this.annotatedService = indexService instanceof AnnotatedServiceInterface
                ? (AnnotatedServiceInterface) indexService
                : null;
        this.indexDefinitionClass = indexDefinitionClass;
        Index index = indexDefinitionClass.getAnnotation(Index.class);
        Objects.requireNonNull(index, "This class does not declare any Index annotation: " + indexDefinitionClass);
        schemaName = index.schema();
        indexName = index.name();
        similarityClass = index.similarityClass();
        Field[] fields = indexDefinitionClass.getDeclaredFields();
        if (fields != null && fields.length > 0) {
            fieldMap = new HashMap<>();
            indexFieldMap = new HashMap<>();
            for (Field field : fields) {
                if (!field.isAnnotationPresent(IndexField.class))
                    continue;
                field.setAccessible(true);
                IndexField indexField = field.getDeclaredAnnotation(IndexField.class);
                String indexName = StringUtils.isEmpty(indexField.name()) ? field.getName() : indexField.name();
                indexFieldMap.put(indexName, indexField);
                fieldMap.put(indexName, field);
            }
        } else {
            fieldMap = null;
            indexFieldMap = null;
        }
    }

    final private void checkParameters() {
        if (StringUtils.isEmpty(schemaName))
            throw new RuntimeException("The schema name is empty");
        if (StringUtils.isEmpty(indexName))
            throw new RuntimeException("The index name is empty");
    }

    /**
     * Create a new schema or update an existing one
     *
     * @return the schema settings
     */
    public SchemaSettingsDefinition createUpdateSchema() {
        checkParameters();
        return indexService.createUpdateSchema(schemaName);
    }

    /**
     * Check if the HTTP response returned a valid code. If not a WebApplicationException is thrown
     *
     * @param response   The HTTP   response
     * @param validCodes The valid HTTP codes
     */
    private void checkHttpResponse(Response response, int... validCodes) {
        if (response == null)
            throw new WebApplicationException("No response");
        if (ArrayUtils.contains(validCodes, response.getStatus()))
            return;
        String message = null;
        Object entity = response.getEntity();
        if (entity != null) {
            try {
                if (entity instanceof HttpEntity)
                    message = EntityUtils.toString((HttpEntity) entity);
                else if (entity instanceof InputStream)
                    message = IOUtils.toString((InputStream) entity);
            } catch (IOException e) {
                message = null;
            }
        }
        if (message == null && response.getStatusInfo() != null)
            message = response.getStatusInfo().getReasonPhrase();
        throw new WebApplicationException(message, response);
    }

    /**
     * Delete the schema
     */
    public void deleteSchema() {
        checkParameters();
        checkHttpResponse(indexService.deleteSchema(schemaName), 200);
    }

    /**
     * Create a new index or update an existing one.
     *
     * @return the index status
     */
    public IndexStatus createUpdateIndex() {
        checkParameters();
        if (StringUtils.isEmpty(similarityClass))
            return indexService.createUpdateIndex(schemaName, indexName);
        IndexSettingsDefinition settings = new IndexSettingsDefinition(similarityClass);
        return indexService.createUpdateIndex(schemaName, indexName, settings);
    }

    public void deleteIndex() {
        checkParameters();
        checkHttpResponse(indexService.deleteIndex(schemaName, indexName), 200);
    }

    /**
     * Set a collection of fields by reading the annotated fields.
     *
     * @return the field map
     */
    public LinkedHashMap<String, FieldDefinition> createUpdateFields() {
        checkParameters();
        final LinkedHashMap<String, FieldDefinition> indexFields = new LinkedHashMap<>();
        if (indexFieldMap != null)
            indexFieldMap.forEach((name, indexField) -> indexFields.put(name, new FieldDefinition(indexField)));
        return indexService.setFields(schemaName, indexName, indexFields);
    }

    /**
     * Post a document to the index
     *
     * @param row the document to index
     */
    public void postDocument(T row) throws IOException, InterruptedException {
        checkParameters();
        Objects.requireNonNull(row, "The document (row) cannot be null");
        if (annotatedService != null)
            annotatedService.postDocument(schemaName, indexName, fieldMap, row);
        else
            indexService.postMappedDocument(schemaName, indexName, newMap(row));
    }

    /**
     * Post a collection of document to the index
     *
     * @param rows a collection of document to index
     */
    public void postDocuments(Collection<T> rows) throws IOException, InterruptedException {
        checkParameters();
        Objects.requireNonNull(rows, "The documents collection (rows) cannot be null");
        if (annotatedService != null)
            annotatedService.postDocuments(schemaName, indexName, fieldMap, rows);
        else
            indexService.postMappedDocuments(schemaName, indexName, newMapCollection(rows));
    }

    /**
     * Update the DocValues of one document
     *
     * @param row a collection of DocValues to update
     */
    public void updateDocumentValues(T row) throws IOException, InterruptedException {
        checkParameters();
        Objects.requireNonNull(row, "The document (row) cannot be null");
        if (annotatedService != null)
            annotatedService.updateDocValues(schemaName, indexName, fieldMap, row);
        else
            indexService.updateMappedDocValues(schemaName, indexName, newMap(row));
    }

    /**
     * Update the DocValues of a collection of document
     *
     * @param rows a collection of document with a collection of DocValues to update
     */
    public void updateDocumentsValues(Collection<T> rows) throws IOException, InterruptedException {
        checkParameters();
        Objects.requireNonNull(rows, "The documents collection (rows) cannot be null");
        if (annotatedService != null)
            annotatedService.updateDocsValues(schemaName, indexName, fieldMap, rows);
        else
            indexService.updateMappedDocsValues(schemaName, indexName, newMapCollection(rows));
    }

    /**
     * @param id The ID of the document
     * @return an filled object or null if the document does not exist
     * @throws ReflectiveOperationException
     */
    public T getDocument(String id) throws ReflectiveOperationException {
        checkParameters();
        Objects.requireNonNull(id, "The id cannot be empty");
        if (annotatedService != null)
            return annotatedService.getDocument(schemaName, indexName, id, fieldMap, indexDefinitionClass);
        else
            return toRecord(indexService.getDocument(schemaName, indexName, id.toString()));
    }

    /**
     * @return the status of the index
     */
    public IndexStatus getIndexStatus() {
        checkParameters();
        return indexService.getIndex(schemaName, indexName);
    }

    /**
     * Execute a search query
     *
     * @param query the query to execute
     * @return the results
     */
    public ResultDefinition.WithObject<T> searchQuery(QueryDefinition query) {
        checkParameters();
        if (annotatedService != null)
            return annotatedService.searchQuery(schemaName, indexName, query, fieldMap, indexDefinitionClass);
        throw new ServerException("Remote annotated Search Query not yet implemented");
    }

    public ResultDefinition.WithMap searchQueryWithMap(QueryDefinition query) {
        checkParameters();
        return indexService.searchQuery(schemaName, indexName, query, false);
    }

    /**
     * Delete the documents matching the query
     *
     * @param query the query to execute
     * @return the results
     */
    public ResultDefinition<?> deleteByQuery(QueryDefinition query) {
        checkParameters();
        return indexService.searchQuery(schemaName, indexName, query, true);
    }

    /**
     * Build a new Map by reading the IndexField annotations
     *
     * @param row the record
     * @return a new Map
     */
    private Map<String, Object> newMap(final T row) {
        final Map<String, Object> map = new HashMap<>();
        fieldMap.forEach(new BiConsumer<String, Field>() {
            @Override
            public void accept(String name, Field field) {
                try {
                    Object value = field.get(row);
                    if (value == null)
                        return;
                    map.put(name, value);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        return map.isEmpty() ? null : map;
    }

    /**
     * Buid a collection of Map by reading the IndexFields of the annotated documents
     *
     * @param rows a collection of records
     * @return a new array of map objects
     */
    private Collection<Map<String, Object>> newMapCollection(final Collection<T> rows) {
        if (rows == null || rows.isEmpty())
            return null;
        final Collection<Map<String, Object>> list = new ArrayList<>(rows.size());
        rows.forEach(row -> list.add(newMap(row)));
        return list;
    }

    private T toRecord(Map<String, Object> fields) throws ReflectiveOperationException {
        if (fields == null)
            return null;
        final T record = indexDefinitionClass.newInstance();
        fields.forEach(new BiConsumer<String, Object>() {
            @Override
            public void accept(String fieldName, Object fieldValue) {
                Field field = fieldMap.get(fieldName);
                if (field == null)
                    return;
                try {
                    field.set(record, fieldValue);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        return record;
    }

}