package org.jai.search.index.impl;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.jai.search.client.SearchClientService;
import org.jai.search.config.ElasticSearchIndexConfig;
import org.jai.search.index.IndexProductDataService;
import org.jai.search.model.Category;
import org.jai.search.model.Product;
import org.jai.search.model.ProductGroup;
import org.jai.search.model.ProductProperty;
import org.jai.search.model.SearchDocumentFieldName;
import org.jai.search.model.SearchFacetName;
import org.jai.search.model.Specification;
import org.jai.search.util.SearchDateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class IndexProductDataServiceImpl implements IndexProductDataService {
private static final Logger logger = LoggerFactory
.getLogger(IndexProductDataServiceImpl.class);
@Autowired
private SearchClientService searchClientService;
@Override
public void indexAllProducts(final ElasticSearchIndexConfig config,
final List<Product> products) {
logger.debug("Indexing bulk data request, for size:" + products.size());
if (products.isEmpty()) {
return;
}
final List<IndexRequestBuilder> requests = new ArrayList<IndexRequestBuilder>();
for (final Product product : products) {
try {
requests.add(getIndexRequestBuilderForAProduct(product,
config.getIndexAliasName(), config.getDocumentType()));
} catch (final Exception ex) {
logger.error(
"Error occurred while creating index document for product with id: "
+ product.getId() + ", moving to next product!",
ex);
}
}
processBulkRequests(requests);
}
@Override
public void indexProduct(final ElasticSearchIndexConfig config,
final String indexName, final Product product) {
String indexNameUsed = indexName;
if (StringUtils.isBlank(indexName)) {
indexNameUsed = config.getIndexAliasName();
}
try {
getIndexRequestBuilderForAProduct(product, indexNameUsed,
config.getDocumentType()).get();
} catch (final Exception ex) {
logger.error(
"Error occurred while creating index document for product.",
ex);
throw new RuntimeException(ex);
}
}
@Override
public void indexProductPropterty(final ElasticSearchIndexConfig config,
final String indexName, final ProductProperty productProperty) {
String indexNameUsed = indexName;
if (StringUtils.isBlank(indexName)) {
indexNameUsed = config.getIndexAliasName();
}
try {
// To parent-child for now.
getIndexRequestBuilderForAProductProperty(null, productProperty,
config, indexNameUsed).get();
} catch (final Exception ex) {
logger.error(
"Error occurred while creating index document for product.",
ex);
throw new RuntimeException(ex);
}
}
@Override
public void indexProductGroup(final ElasticSearchIndexConfig config,
final String indexName, final ProductGroup productGroup) {
String indexNameUsed = indexName;
if (StringUtils.isBlank(indexName)) {
indexNameUsed = config.getIndexAliasName();
}
try {
// To parent-child for now.
getIndexRequestBuilderForAProductGroup(productGroup, config,
indexNameUsed).get();
} catch (final Exception ex) {
logger.error(
"Error occurred while creating index document for product.",
ex);
throw new RuntimeException(ex);
}
}
@Override
public boolean isProductExists(final ElasticSearchIndexConfig config,
final Long productId) {
return searchClientService.getClient().prepareGet()
.setIndex(config.getIndexAliasName())
.setId(String.valueOf(productId)).get().isExists();
}
@Override
public void deleteProduct(final ElasticSearchIndexConfig config,
final Long productId) {
searchClientService
.getClient()
.prepareDelete(config.getIndexAliasName(),
config.getDocumentType(), String.valueOf(productId))
.get();
}
public void indexAllProductGroupData(final ElasticSearchIndexConfig config,
final List<ProductGroup> productGroups,
final boolean parentRelationShip) {
for (final ProductGroup productGroup : productGroups) {
final List<IndexRequestBuilder> requests = new ArrayList<IndexRequestBuilder>();
try {
requests.add(getIndexRequestBuilderForAProductGroup(
productGroup, config, null));
// Index all products data also with parent
for (final Product product : productGroup.getProducts()) {
final IndexRequestBuilder indexRequestBuilderForAProduct = getIndexRequestBuilderForAProduct(
product, config.getIndexAliasName(),
config.getDocumentType());
if (parentRelationShip) {
indexRequestBuilderForAProduct.setParent(String
.valueOf(productGroup.getId()));
}
requests.add(indexRequestBuilderForAProduct);
for (final ProductProperty productProperty : product
.getProductProperties()) {
final IndexRequestBuilder indexRequestBuilderForAProductProperty = getIndexRequestBuilderForAProductProperty(
product, productProperty, config, null);
if (parentRelationShip) {
indexRequestBuilderForAProductProperty
.setParent(String.valueOf(product.getId()));
}
requests.add(indexRequestBuilderForAProductProperty);
}
}
} catch (final Exception ex) {
logger.error(
"Error occurred while creating index document for product with id: "
+ productGroup.getId()
+ ", moving to next product!", ex);
}
processBulkRequests(requests);
// processBulkRequestsUsingAkka(requests);
// requests.clear();
}
}
private IndexRequestBuilder getIndexRequestBuilderForAProduct(
final Product product, final String indexName,
final String documentType) throws IOException {
final XContentBuilder contentBuilder = getXContentBuilderForAProduct(product);
final IndexRequestBuilder indexRequestBuilder = searchClientService
.getClient().prepareIndex(indexName, documentType,
String.valueOf(product.getId()));
indexRequestBuilder.setSource(contentBuilder);
return indexRequestBuilder;
}
private IndexRequestBuilder getIndexRequestBuilderForAProductProperty(
final Product product, final ProductProperty productProperty,
final ElasticSearchIndexConfig config, final String indexNameUsed)
throws IOException {
final XContentBuilder contentBuilder = getXContentBuilderForAProductProperty(productProperty);
final String documentId = (product != null ? String.valueOf(product
.getId()) : "")
+ String.valueOf(productProperty.getId())
+ "0000";
logger.debug("Generated XContentBuilder for document id {} is {}",
new Object[] { documentId,
contentBuilder.prettyPrint().string() });
final IndexRequestBuilder indexRequestBuilder = searchClientService
.getClient().prepareIndex(indexNameUsed,
config.getPropertiesDocumentType(), documentId);
indexRequestBuilder.setSource(contentBuilder);
return indexRequestBuilder;
}
private IndexRequestBuilder getIndexRequestBuilderForAProductGroup(
final ProductGroup productGroup,
final ElasticSearchIndexConfig config, final String indexName)
throws IOException {
final XContentBuilder contentBuilder = getXContentBuilderForAProductGroup(productGroup);
logger.debug("Generated XContentBuilder for document id {} is {}",
new Object[] { productGroup.getId(),
contentBuilder.prettyPrint().string() });
final IndexRequestBuilder indexRequestBuilder = searchClientService
.getClient().prepareIndex(indexName,
config.getGroupDocumentType(),
String.valueOf(productGroup.getId()));
indexRequestBuilder.setSource(contentBuilder);
return indexRequestBuilder;
}
private XContentBuilder getXContentBuilderForAProduct(final Product product)
throws IOException {
XContentBuilder contentBuilder = null;
try {
contentBuilder = jsonBuilder().prettyPrint().startObject();
contentBuilder
.field(SearchDocumentFieldName.TITLE.getFieldName(),
product.getTitle())
.field(SearchDocumentFieldName.DESCRIPTION.getFieldName(),
product.getDescription())
.field(SearchDocumentFieldName.PRICE.getFieldName(),
product.getPrice())
.field(SearchDocumentFieldName.KEYWORDS.getFieldName(),
product.getKeywords())
.field(SearchDocumentFieldName.AVAILABLE_DATE
.getFieldName(),
SearchDateUtils.formatDate(product.getAvailableOn()))
.field(SearchDocumentFieldName.SOLD_OUT.getFieldName(),
product.isSoldOut())
.field(SearchDocumentFieldName.BOOSTFACTOR.getFieldName(),
product.getBoostFactor());
if (product.getCategories().size() > 0) {
// Add category data
final Map<Integer, Set<Category>> levelMap = getContentCategoryLevelMap(product
.getCategories());
contentBuilder
.startArray(SearchDocumentFieldName.CATEGORIES_ARRAY
.getFieldName());
for (final Entry<Integer, Set<Category>> contentCategoryEntrySet : levelMap
.entrySet()) {
for (final Category category : contentCategoryEntrySet
.getValue()) {
final String name = category.getType()
+ SearchFacetName.HIERARCHICAL_DATA_LEVEL_STRING
+ contentCategoryEntrySet.getKey();
contentBuilder
.startObject()
.field(name
+ "."
+ SearchDocumentFieldName.FACET
.getFieldName(),
category.getName())
// .field(name +
// SearchFacetName.SEQUENCED_FIELD_SUFFIX,
// getSequenceNumberOrdering(contentCategory) +
// categoryTranalationText)
.field(name
+ "."
+ SearchDocumentFieldName.FACETFILTER
.getFieldName(),
category.getName().toLowerCase())
.field(name
+ "."
+ SearchDocumentFieldName.SUGGEST
.getFieldName(),
category.getName().toLowerCase())
.endObject();
}
}
contentBuilder.endArray();
}
if (product.getSpecifications().size() > 0) {
// Index specifications
contentBuilder
.startArray(SearchDocumentFieldName.SPECIFICATIONS
.getFieldName());
for (final Specification specification : product
.getSpecifications()) {
contentBuilder
.startObject()
.field(SearchDocumentFieldName.RESOLUTION
.getFieldName(),
specification.getResolution())
.field(SearchDocumentFieldName.MEMORY
.getFieldName(),
specification.getMemory()).endObject();
}
contentBuilder.endArray();
}
contentBuilder.endObject();
} catch (final IOException ex) {
logger.error(ex.getMessage());
throw new RuntimeException(
"Error occured while creating product gift json document!",
ex);
}
logger.debug("Generated XContentBuilder for document id {} is {}",
new Object[] { product.getId(),
contentBuilder.prettyPrint().string() });
return contentBuilder;
}
private XContentBuilder getXContentBuilderForAProductProperty(
final ProductProperty productProperty) {
XContentBuilder contentBuilder = null;
try {
contentBuilder = jsonBuilder().prettyPrint().startObject();
contentBuilder.field(SearchDocumentFieldName.SIZE.getFieldName(),
productProperty.getSize()).field(
SearchDocumentFieldName.COLOR.getFieldName(),
productProperty.getColor());
contentBuilder.endObject();
} catch (final IOException ex) {
logger.error(ex.getMessage());
throw new RuntimeException(
"Error occured while creating product gift json document!",
ex);
}
return contentBuilder;
}
private XContentBuilder getXContentBuilderForAProductGroup(
final ProductGroup productGroup) {
XContentBuilder contentBuilder = null;
try {
contentBuilder = jsonBuilder().prettyPrint().startObject();
contentBuilder.field(SearchDocumentFieldName.TITLE.getFieldName(),
productGroup.getGroupTitle()).field(
SearchDocumentFieldName.DESCRIPTION.getFieldName(),
productGroup.getGroupDescription());
contentBuilder.endObject();
} catch (final IOException ex) {
logger.error(ex.getMessage());
throw new RuntimeException(
"Error occured while creating product gift json document!",
ex);
}
return contentBuilder;
}
private Map<Integer, Set<Category>> getContentCategoryLevelMap(
final List<Category> categories) {
final Map<Integer, Set<Category>> levelMap = new HashMap<Integer, Set<Category>>();
for (final Category contentCategory : categories) {
final int defaultTopLevelCategoryIndex = 1;
final int levelInHierarchy = getCategoryLevelInHierarchy(
contentCategory, defaultTopLevelCategoryIndex);
for (int categoryLevelCounter = levelInHierarchy; categoryLevelCounter <= levelInHierarchy
&& categoryLevelCounter >= defaultTopLevelCategoryIndex; categoryLevelCounter--) {
processCategoryAtLevel(
levelMap,
findCategoryAtLevel(contentCategory, levelInHierarchy,
categoryLevelCounter), categoryLevelCounter);
}
}
return levelMap;
}
private Category findCategoryAtLevel(final Category contentCategory,
final int currentCategoryLevel, final int counter) {
if (currentCategoryLevel == counter) {
return contentCategory;
}
final int nextCounter = counter + 1;
return findCategoryAtLevel(contentCategory.getParentCategory(),
currentCategoryLevel, nextCounter);
}
private int getCategoryLevelInHierarchy(final Category contentCategory,
final int level) {
if (contentCategory.getParentCategory() == null) {
return level;
}
final int nextLevel = level + 1;
return getCategoryLevelInHierarchy(contentCategory.getParentCategory(),
nextLevel);
}
private void processCategoryAtLevel(
final Map<Integer, Set<Category>> levelMap,
final Category contentCategory, final int categoryLevel) {
final Set<Category> categoryLevelSet = getCategoryLevelSet(levelMap,
categoryLevel);
categoryLevelSet.add(contentCategory);
}
private Set<Category> getCategoryLevelSet(
final Map<Integer, Set<Category>> levelMap, final int level) {
final Integer valueOf = Integer.valueOf(level);
Set<Category> set = levelMap.get(valueOf);
if (set == null) {
set = new HashSet<Category>();
levelMap.put(valueOf, set);
}
return set;
}
protected BulkResponse processBulkRequests(
final List<IndexRequestBuilder> requests) {
if (requests.size() > 0) {
final BulkRequestBuilder bulkRequest = searchClientService
.getClient().prepareBulk();
for (final IndexRequestBuilder indexRequestBuilder : requests) {
bulkRequest.add(indexRequestBuilder);
}
logger.debug("Executing bulk index request for size:"
+ requests.size());
final BulkResponse bulkResponse = bulkRequest.execute().actionGet();
logger.debug("Bulk operation data index response total items is:"
+ bulkResponse.getItems().length);
if (bulkResponse.hasFailures()) {
// process failures by iterating through each bulk response item
logger.error("bulk operation indexing has failures:"
+ bulkResponse.buildFailureMessage());
}
return bulkResponse;
} else {
logger.debug("Executing bulk index request for size: 0");
return null;
}
}
}