/* * Copyright 2015 herd contributors * * 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 org.finra.herd.service.impl; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import com.google.common.collect.ImmutableSet; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import org.finra.herd.dao.IndexSearchDao; import org.finra.herd.dao.helper.ElasticsearchHelper; import org.finra.herd.model.api.xml.IndexSearchFilter; import org.finra.herd.model.api.xml.IndexSearchRequest; import org.finra.herd.model.api.xml.IndexSearchResponse; import org.finra.herd.model.api.xml.IndexSearchResultTypeKey; import org.finra.herd.model.api.xml.TagKey; import org.finra.herd.model.jpa.TagEntity; import org.finra.herd.service.FacetFieldValidationService; import org.finra.herd.service.IndexSearchService; import org.finra.herd.service.SearchableService; import org.finra.herd.service.helper.IndexSearchResultTypeHelper; import org.finra.herd.service.helper.TagDaoHelper; import org.finra.herd.service.helper.TagHelper; /** * IndexSearchServiceImpl is the implementation of the IndexSearchService and includes an indexSearch method that will handle search requests against a search * index. */ @Service public class IndexSearchServiceImpl implements IndexSearchService, SearchableService, FacetFieldValidationService { /** * Constant to hold the display name option for the indexSearch */ private static final String DISPLAY_NAME_FIELD = "displayname"; /** * The minimum allowable length of a search term */ private static final int SEARCH_TERM_MINIMUM_ALLOWABLE_LENGTH = 3; /** * Constant to hold the short description option for the indexSearch */ private static final String SHORT_DESCRIPTION_FIELD = "shortdescription"; @Autowired private IndexSearchDao indexSearchDao; @Autowired private TagHelper tagHelper; @Autowired private TagDaoHelper tagDaoHelper; @Autowired private IndexSearchResultTypeHelper resultTypeHelper; @Override public IndexSearchResponse indexSearch(final IndexSearchRequest request, final Set<String> fields) { // Validate the search response fields validateSearchResponseFields(fields); // Validate the search term validateIndexSearchRequestSearchTerm(request.getSearchTerm()); // Validate the index search filters if specified in the request if (request.getIndexSearchFilters() != null) { validateIndexSearchFilters(request.getIndexSearchFilters()); } Set<String> facetFields = new HashSet<>(); if (CollectionUtils.isNotEmpty(request.getFacetFields())) { facetFields.addAll(validateFacetFields(new HashSet<>(request.getFacetFields()))); //set the facets fields after validation request.setFacetFields(new ArrayList<>(facetFields)); } return indexSearchDao.indexSearch(request, fields); } /** * Private method to validate the index search request search term. * * @param indexSearchTerm the index search term string */ private void validateIndexSearchRequestSearchTerm(final String indexSearchTerm) { // A search term must be provided Assert.notNull(indexSearchTerm, "A search term must be specified."); // The following characters will be react like spaces during the search: '-' and '_' // Confirm that the search term is long enough Assert.isTrue(indexSearchTerm.replace('-', ' ').replace('_', ' ').trim().length() >= SEARCH_TERM_MINIMUM_ALLOWABLE_LENGTH, "The search term length must be at least " + SEARCH_TERM_MINIMUM_ALLOWABLE_LENGTH + " characters."); } /** * Validates the specified index search filters. * * @param indexSearchFilters the index search filters */ private void validateIndexSearchFilters(List<IndexSearchFilter> indexSearchFilters) { // Validate that the search filters list is not empty Assert.notEmpty(indexSearchFilters, "At least one index search filter must be specified."); for (IndexSearchFilter searchFilter : indexSearchFilters) { // Silently skip a search filter which is null if (null != searchFilter) { // Validate that each search filter has at least one index search key Assert.notEmpty(searchFilter.getIndexSearchKeys(), "At least one index search key must be specified."); // Guard against a single null element in the index search keys list if (null != searchFilter.getIndexSearchKeys().get(0)) { // Get the instance type of the key in the search filter, match all other keys with this Class<?> expectedInstanceType = searchFilter.getIndexSearchKeys().get(0).getIndexSearchResultTypeKey() != null ? IndexSearchResultTypeKey.class : TagKey.class; searchFilter.getIndexSearchKeys().forEach(indexSearchKey -> { // Validate that each search key has either an index search result type key or a tag key Assert.isTrue((indexSearchKey.getIndexSearchResultTypeKey() != null) ^ (indexSearchKey.getTagKey() != null), "Exactly one instance of index search result type key or tag key must be specified."); Class<?> actualInstanceType = indexSearchKey.getIndexSearchResultTypeKey() != null ? IndexSearchResultTypeKey.class : TagKey.class; // Validate that search keys within the same filter have either index search result type keys or tag keys Assert.isTrue(expectedInstanceType.equals(actualInstanceType), "Index search keys should be a homogeneous list of either index search result type keys or tag keys."); // Validate tag key if present if (indexSearchKey.getTagKey() != null) { tagHelper.validateTagKey(indexSearchKey.getTagKey()); // Validates that a tag entity exists for the specified tag key and gets the actual key from the database // We then modify the index search filter key to use the actual values because it eventually becomes a filter query and it will not // automatically be case-sensitivity and whitespace resilient. TagEntity actualTagEntity = tagDaoHelper.getTagEntity(indexSearchKey.getTagKey()); TagKey tagKey = new TagKey(actualTagEntity.getTagType().getCode(), actualTagEntity.getTagCode()); indexSearchKey.setTagKey(tagKey); } // Validate search result type key if present if (indexSearchKey.getIndexSearchResultTypeKey() != null) { resultTypeHelper.validateIndexSearchResultTypeKey(indexSearchKey.getIndexSearchResultTypeKey()); } }); } } } } @Override public Set<String> getValidSearchResponseFields() { return ImmutableSet.of(SHORT_DESCRIPTION_FIELD, DISPLAY_NAME_FIELD); } @Override public Set<String> getValidFacetFields() { return ImmutableSet.of(ElasticsearchHelper.TAG_FACET, ElasticsearchHelper.RESULT_TYPE_FACET); } }