org.opencommercesearch.AbstractSearchServer.java Source code

Java tutorial

Introduction

Here is the source code for org.opencommercesearch.AbstractSearchServer.java

Source

package org.opencommercesearch;

/*
* Licensed to OpenCommerceSearch under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. OpenCommerceSearch licenses this
* file to you 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.
*/

import atg.multisite.Site;
import atg.multisite.SiteContextManager;
import atg.nucleus.GenericService;
import atg.repository.Repository;
import atg.repository.RepositoryException;
import atg.repository.RepositoryItem;
import atg.repository.RepositoryView;
import atg.repository.rql.RqlStatement;

import org.apache.commons.lang.StringUtils;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.request.DocumentAnalysisRequest;
import org.apache.solr.client.solrj.request.FieldAnalysisRequest;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.client.solrj.response.FacetField;
import org.apache.solr.client.solrj.response.FacetField.Count;
import org.apache.solr.client.solrj.response.GroupCommand;
import org.apache.solr.client.solrj.response.GroupResponse;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.SolrPingResponse;
import org.apache.solr.client.solrj.response.SpellCheckResponse;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.GroupCollapseParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.SolrException;
import org.opencommercesearch.client.impl.Facet;
import org.opencommercesearch.client.impl.Facet.Filter;
import org.opencommercesearch.repository.RedirectRuleProperty;
import org.opencommercesearch.repository.SearchRepositoryItemDescriptor;

import static org.opencommercesearch.SearchServerException.create;
import static org.opencommercesearch.SearchServerException.Code.*;
import static org.opencommercesearch.SearchServerException.ExportSynonymException;

import java.io.IOException;
import java.util.*;

/**
 *
 *
 * @author gsegura
 * @author rmerizalde
 */
public abstract class AbstractSearchServer<T extends SolrServer> extends GenericService implements SearchServer {

    public static final String SCORE = "score";

    // Current cloud implementation seem to have a bug. It support the
    // collection property but once a collection is used it sticks to it
    private Map<String, T> catalogSolrServers = new HashMap<String, T>();
    private Map<String, T> rulesSolrServers = new HashMap<String, T>();
    private Map<String, T> autocompleteSolrServers = new HashMap<String, T>();
    private String catalogCollection;
    private String rulesCollection;
    private String autocompleteCollection;
    private String catalogConfig;
    private String rulesConfig;
    private String autocompleteConfig;
    private String minimumMatch;
    private Repository searchRepository;
    private RqlStatement synonymListRql;
    private RqlStatement ruleCountRql;
    private RqlStatement ruleRql;
    private RqlStatement synonymRql;
    private int ruleBatchSize;
    private RulesBuilder rulesBuilder;
    private boolean isGroupSortingEnabled;

    private static final String Q_ALT = "q.alt";
    private static final String BRAND_ID = "brandId";
    private static final String CATEGORY_PATH = "categoryPath";

    public void setCatalogSolrServer(T catalogSolrServer, Locale locale) {
        catalogSolrServers.put(locale.getLanguage(), catalogSolrServer);
    }

    public T getCatalogSolrServer(Locale locale) {
        return catalogSolrServers.get(locale.getLanguage());
    }

    public void setRulesSolrServer(T rulesSolrServer, Locale locale) {
        rulesSolrServers.put(locale.getLanguage(), rulesSolrServer);
    }

    public T getRulesSolrServer(Locale locale) {
        return rulesSolrServers.get(locale.getLanguage());
    }

    public void setAutocompleteSolrServers(T autocompleteSolrServer, Locale locale) {
        autocompleteSolrServers.put(locale.getLanguage(), autocompleteSolrServer);
    }

    public T getAutocompleteSolrServers(Locale locale) {
        return autocompleteSolrServers.get(locale.getLanguage());
    }

    public T getSolrServer(String collection, Locale locale) {
        if (rulesCollection != null && rulesCollection.equals(collection)) {
            return getRulesSolrServer(locale);
        } else if (autocompleteCollection != null && autocompleteCollection.equals(collection)) {
            return getAutocompleteSolrServers(locale);
        }
        return getCatalogSolrServer(locale);
    }

    public String getCollectionName(String collection, Locale locale) {
        if (rulesCollection != null && rulesCollection.equals(collection)) {
            return getRulesCollection(locale);
        } else if (autocompleteCollection != null && autocompleteCollection.equals(collection)) {
            return getAutocompleteCollection(locale);
        }
        return getCatalogCollection(locale);
    }

    public String getCatalogCollection() {
        return catalogCollection;
    }

    public String getCatalogCollection(Locale locale) {
        return catalogCollection + "_" + locale.getLanguage();
    }

    public void setCatalogCollection(String catalogCollection) {
        this.catalogCollection = catalogCollection;
    }

    public String getRulesCollection() {
        return rulesCollection;
    }

    public String getRulesCollection(Locale locale) {
        return rulesCollection + "_" + locale.getLanguage();
    }

    public String getAutocompleteCollection() {
        return autocompleteCollection;
    }

    public String getAutocompleteCollection(Locale locale) {
        //TODO gsegura figure out if we want to support autocomplete per language locale
        //return autocompleteCollection + "_" + locale.getLanguage();
        return autocompleteCollection;
    }

    public String getMinimumMatch() {
        return minimumMatch;
    }

    public void setMinimumMatch(String minimumMatch) {
        this.minimumMatch = minimumMatch;
    }

    public String getCatalogConfig() {
        return catalogConfig;
    }

    public void setCatalogConfig(String catalogConfig) {
        this.catalogConfig = catalogConfig;
    }

    public String getRulesConfig() {
        return rulesConfig;
    }

    public void setRulesConfig(String rulesConfig) {
        this.rulesConfig = rulesConfig;
    }

    public void setRulesCollection(String ruleCollection) {
        this.rulesCollection = ruleCollection;
    }

    public String getAutocompleteConfig() {
        return autocompleteConfig;
    }

    public void setAutocompleteConfig(String autocompleteConfig) {
        this.autocompleteConfig = autocompleteConfig;
    }

    public void setAutocompleteCollection(String autocompleteCollection) {
        this.autocompleteCollection = autocompleteCollection;
    }

    public Repository getSearchRepository() {
        return searchRepository;
    }

    public void setSearchRepository(Repository searchRepository) {
        this.searchRepository = searchRepository;
    }

    public RqlStatement getSynonymListRql() {
        return synonymListRql;
    }

    public void setSynonymListRql(RqlStatement synonymListRql) {
        this.synonymListRql = synonymListRql;
    }

    public RqlStatement getRuleCountRql() {
        return ruleCountRql;
    }

    public void setRuleCountRql(RqlStatement ruleCountRql) {
        this.ruleCountRql = ruleCountRql;
    }

    public RqlStatement getRuleRql() {
        return ruleRql;
    }

    public void setRuleRql(RqlStatement ruleRql) {
        this.ruleRql = ruleRql;
    }

    public RqlStatement getSynonymRql() {
        return synonymRql;
    }

    public void setSynonymRql(RqlStatement synonymRql) {
        this.synonymRql = synonymRql;
    }

    public int getRuleBatchSize() {
        return ruleBatchSize;
    }

    public void setRuleBatchSize(int ruleBatchSize) {
        this.ruleBatchSize = ruleBatchSize;
    }

    public RulesBuilder getRulesBuilder() {
        return rulesBuilder;
    }

    public void setRulesBuilder(RulesBuilder rulesBuilder) {
        this.rulesBuilder = rulesBuilder;
    }

    public boolean isGroupSortingEnabled() {
        return isGroupSortingEnabled;
    }

    public void setGroupSortingEnabled(boolean groupSortingEnabled) {
        isGroupSortingEnabled = groupSortingEnabled;
    }

    @Override
    public SearchResponse browse(BrowseOptions options, SolrQuery query, FilterQuery... filterQueries)
            throws SearchServerException {
        return browse(options, query, SiteContextManager.getCurrentSite(), Locale.US, filterQueries);
    }

    @Override
    public SearchResponse browse(BrowseOptions options, SolrQuery query, Locale locale,
            FilterQuery... filterQueries) throws SearchServerException {
        return browse(options, query, SiteContextManager.getCurrentSite(), locale, filterQueries);
    }

    @Override
    public SearchResponse browse(BrowseOptions options, SolrQuery query, Site site, FilterQuery... filterQueries)
            throws SearchServerException {
        return browse(options, query, SiteContextManager.getCurrentSite(), Locale.US, filterQueries);
    }

    @Override
    public SearchResponse browse(BrowseOptions options, SolrQuery query, Site site, Locale locale,
            FilterQuery... filterQueries) throws SearchServerException {

        boolean hasCategoryId = StringUtils.isNotBlank(options.getCategoryId());
        boolean hasCategoryPath = StringUtils.isNotBlank(options.getCategoryPath());
        boolean hasBrandId = StringUtils.isNotBlank(options.getBrandId());
        boolean addCategoryGraph = (options.isFetchCategoryGraph()
                || (hasBrandId && options.isFetchProducts() && !hasCategoryId)) && !options.isRuleBasedPage();

        String categoryPath = null;

        if (hasCategoryPath) {
            categoryPath = options.getCategoryPath();
        } else {
            categoryPath = options.getCatalogId() + ".";
        }

        if (options.isRuleBasedPage()) {
            //handle rule based pages
            String filter = rulesBuilder.buildRulesFilter(options.getCategoryId(), locale);
            query.addFilterQuery(filter);
            query.setParam("q", "*:*");

        } else {
            //handle brand, category or onsale pages                
            if (addCategoryGraph) {
                query.setFacetPrefix(CATEGORY_PATH, categoryPath);
                query.addFacetField(CATEGORY_PATH);
                query.set("f.categoryPath.facet.limit", options.getMaxCategoryResults());
            }

            if (!options.isFetchProducts()) {
                query.setRows(0);
            }

            List<String> queryAltParams = new ArrayList<String>();

            if (hasCategoryId) {
                queryAltParams.add(CATEGORY_PATH + ":" + categoryPath);
                query.setParam("q", "");
            }

            if (hasBrandId) {
                queryAltParams.add(BRAND_ID + ":" + options.getBrandId());
                query.setParam("q", "");
            }

            if (options.isOnSale()) {
                queryAltParams.add("onsale" + locale.getCountry() + ":true");
            }

            if (queryAltParams.size() > 0) {

                query.set(Q_ALT, "(" + StringUtils.join(queryAltParams, " AND ") + ")");
            }
        }

        RepositoryItem catalog = null;
        if (site != null) {
            catalog = (RepositoryItem) site.getPropertyValue("defaultCatalog");
        }

        SearchResponse response = null;
        if (options.isRuleBasedPage()) {
            response = doSearch(query, site, catalog, locale, false, true, categoryPath, options.isOnSale(),
                    options.getBrandId(), filterQueries);
        } else if (hasCategoryPath) {
            response = doSearch(query, site, catalog, locale, false, false, categoryPath, options.isOnSale(),
                    options.getBrandId(), filterQueries);
        } else {
            response = doSearch(query, site, catalog, locale, false, false, null, options.isOnSale(),
                    options.getBrandId(), filterQueries);
        }

        if (addCategoryGraph) {
            response.setCategoryGraph(
                    createCategoryGraph(response, options.getCategoryPath(), options.getCatalogId(),
                            options.getCategoryId(), options.getDepthLimit(), options.getSeparator()));
        }

        return response;
    }

    @Override
    public SearchResponse search(SolrQuery query, FilterQuery... filterQueries) throws SearchServerException {
        return search(query, SiteContextManager.getCurrentSite(), Locale.US, filterQueries);
    }

    @Override
    public SearchResponse search(SolrQuery query, Locale locale, FilterQuery... filterQueries)
            throws SearchServerException {
        return search(query, SiteContextManager.getCurrentSite(), locale, filterQueries);
    }

    @Override
    public SearchResponse search(SolrQuery query, Site site, FilterQuery... filterQueries)
            throws SearchServerException {
        return search(query, site, Locale.US, filterQueries);
    }

    @Override
    public SearchResponse search(SolrQuery query, Site site, Locale locale, FilterQuery... filterQueries)
            throws SearchServerException {
        RepositoryItem catalog = null;
        if (site != null) {
            catalog = (RepositoryItem) site.getPropertyValue("defaultCatalog");
        }
        return search(query, site, catalog, locale, filterQueries);
    }

    @Override
    public SearchResponse search(SolrQuery query, Site site, RepositoryItem catalog, FilterQuery... filterQueries)
            throws SearchServerException {
        return search(query, site, catalog, Locale.ENGLISH, filterQueries);
    }

    @Override
    public SearchResponse search(SolrQuery query, Site site, RepositoryItem catalog, Locale locale,
            FilterQuery... filterQueries) throws SearchServerException {
        return doSearch(query, site, catalog, locale, true, false, null, false, null, filterQueries);
    }

    @Override
    public Facet getFacet(Site site, Locale locale, String fieldFacet, int facetLimit, int depthLimit,
            String separator, FilterQuery... filterQueries) throws SearchServerException {
        try {
            SolrQuery query = new SolrQuery();
            query.setRows(0);
            query.setQuery("*:*");
            query.addFacetField(fieldFacet);
            query.setFacetLimit(facetLimit);
            query.setFacetMinCount(1);
            query.addFilterQuery("country:" + locale.getCountry());

            RepositoryItem catalog = (RepositoryItem) site.getPropertyValue("defaultCatalog");
            String catalogId = catalog.getRepositoryId();
            query.setFacetPrefix(CATEGORY_PATH, catalogId + ".");
            query.addFilterQuery(CATEGORY_PATH + ":" + catalogId);

            if (filterQueries != null) {
                for (FilterQuery filterQuery : filterQueries) {
                    query.addFilterQuery(filterQuery.toString());
                }
            }

            QueryResponse queryResponse = getCatalogSolrServer(locale).query(query);
            Facet facet = null;
            if (queryResponse != null && queryResponse.getFacetFields() != null) {
                FacetField facetField = queryResponse.getFacetField(fieldFacet);
                if (facetField != null) {
                    List<Count> values = facetField.getValues();
                    if (values != null && !values.isEmpty()) {
                        facet = new Facet();
                        facet.setName(StringUtils.capitalize(fieldFacet));
                        List<Filter> filters = new ArrayList<Facet.Filter>();

                        boolean filterByDepth = depthLimit > 0 && StringUtils.isNotBlank(separator);
                        for (Count count : values) {

                            if (filterByDepth
                                    && StringUtils.countMatches(count.getName(), separator) > depthLimit) {
                                continue;
                            }

                            Filter filter = new Filter();
                            filter.setName(count.getName());
                            filter.setCount(count.getCount());
                            filter.setFilterQuery(count.getAsFilterQuery());
                            filter.setFilterQueries(count.getAsFilterQuery());
                            filters.add(filter);
                        }
                        facet.setFilters(filters);
                    }
                }
            }
            return facet;
        } catch (SolrServerException ex) {
            throw create(SEARCH_EXCEPTION, ex);
        } catch (SolrException ex) {
            throw create(SEARCH_EXCEPTION, ex);
        }
    }

    private SearchResponse doSearch(SolrQuery query, Site site, RepositoryItem catalog, Locale locale,
            boolean isSearch, boolean isRuleBasedPage, String categoryPath, boolean isOutletPage, String brandId,
            FilterQuery... filterQueries) throws SearchServerException {
        if (site == null) {
            throw new IllegalArgumentException("Missing site");
        }
        if (catalog == null) {
            throw new IllegalArgumentException("Missing catalog");
        }
        long startTime = System.currentTimeMillis();

        query.addFacetField("category");
        query.set("facet.mincount", 1);

        RuleManager ruleManager = new RuleManager(getSearchRepository(), getRulesBuilder(),
                getRulesSolrServer(locale));
        if ((query.getRows() != null && query.getRows() > 0)
                || (query.get("group") != null && query.getBool("group"))) {
            setGroupParams(query, locale);
            setFieldListParams(query, locale.getCountry(), catalog.getRepositoryId());
            try {
                ruleManager.setRuleParams(query, isSearch, isRuleBasedPage, categoryPath, filterQueries, catalog,
                        isOutletPage, brandId);

                if (ruleManager.getRules().containsKey(SearchRepositoryItemDescriptor.REDIRECT_RULE)) {
                    Map<String, List<RepositoryItem>> rules = ruleManager.getRules();
                    List<RepositoryItem> redirects = rules.get(SearchRepositoryItemDescriptor.REDIRECT_RULE);
                    if (redirects != null) {
                        RepositoryItem redirect = redirects.get(0);
                        return new SearchResponse(query, null, null, null,
                                (String) redirect.getPropertyValue(RedirectRuleProperty.URL), null, true);
                    }
                }

            } catch (RepositoryException ex) {
                if (isLoggingError()) {
                    logError("Unable to load search rules: " + ex.getMessage());
                }
                throw create(SEARCH_EXCEPTION, ex);
            } catch (SolrServerException ex) {
                if (isLoggingError()) {
                    logError("Unable to load search rules: " + ex.getMessage());
                }
                throw create(SEARCH_EXCEPTION, ex);
            } catch (SolrException ex) {
                if (isLoggingError()) {
                    logError("Unable to load search rules: " + ex.getMessage());
                }
                throw create(SEARCH_EXCEPTION, ex);
            }
        } else {
            ruleManager.setFilterQueries(filterQueries, catalog.getRepositoryId(), query);
        }

        try {
            QueryResponse queryResponse = getCatalogSolrServer(locale).query(query);

            String correctedTerm = null;
            boolean matchesAll = true;

            //if no results, check for spelling errors
            if (query.getRows() > 0 && isEmptySearch(queryResponse.getGroupResponse())
                    && StringUtils.isNotEmpty(query.getQuery())) {

                SpellCheckResponse spellCheckResponse = queryResponse.getSpellCheckResponse();
                //try to do searching for the corrected term matching all terms (q.op=AND)
                QueryResponse tentativeResponse = handleSpellCheck(spellCheckResponse, getCatalogSolrServer(locale),
                        query, "AND");
                if (tentativeResponse != null) {
                    //if we got results, set the corrected term variable and proceed to return the results
                    queryResponse = tentativeResponse;
                    correctedTerm = spellCheckResponse.getCollatedResult();
                } else {
                    //if we didn't got any response, try doing another search matching any term (q.op=OR)
                    tentativeResponse = handleSpellCheck(spellCheckResponse, getCatalogSolrServer(locale), query,
                            "OR");
                    if (tentativeResponse != null) {
                        //if we got results for the match any term scenario. Set similar results to true
                        //and set the corrected term.
                        queryResponse = tentativeResponse;
                        matchesAll = false;
                        correctedTerm = query.getQuery();
                    }
                }

            }

            long searchTime = System.currentTimeMillis() - startTime;
            if (isLoggingDebug()) {
                logDebug("Search time is " + searchTime + ", search engine time is " + queryResponse.getQTime());
            }

            SearchResponse searchResponse = new SearchResponse(query, queryResponse, ruleManager, filterQueries,
                    null, correctedTerm, matchesAll);
            searchResponse.setRuleQueryTime(ruleManager.getLoadRulesTime());
            return searchResponse;
        } catch (SolrServerException ex) {
            throw create(SEARCH_EXCEPTION, ex);
        } catch (SolrException ex) {
            throw create(SEARCH_EXCEPTION, ex);
        }

    }

    public void setGroupParams(SolrQuery query, Locale locale) {
        query.set("group", true);
        query.set("group.ngroups", true);
        query.set("group.limit", 50);
        query.set("group.field", "productId");
        query.set("group.facet", false);

        if (isGroupSortingEnabled()) {
            List<SolrQuery.SortClause> clauses = query.getSorts();
            boolean isSortByScore = false;

            if (clauses.size() > 0) {
                for (SolrQuery.SortClause clause : clauses) {
                    if (SCORE.equals(clause.getItem())) {
                        isSortByScore = true;
                    }
                }
            } else {
                isSortByScore = true;
            }

            if (isSortByScore) {
                // break ties with custom sort field
                query.set("group.sort",
                        "isCloseout asc, salePrice" + locale.getCountry() + " asc, sort asc, score desc");
            }
        }
    }

    /**
     * Sets the list of fields that should be returned from search.
     * @param query Current SolrQuery being created.
     * @param country Current country code
     * @param catalog Current catalog code
     */
    private void setFieldListParams(SolrQuery query, String country, String catalog) {
        String listPrice = "listPrice" + country;
        String salePrice = "salePrice" + country;
        String discountPercent = "discountPercent" + country;

        if (getCatalogCollection().trim().equalsIgnoreCase("catalogEvaluation")) {
            query.setFields("id", "productId", "title", "brand", "isToos", listPrice, salePrice, discountPercent,
                    "url" + country, "reviewAverage", "reviews", "isPastSeason", "freeGift" + catalog, "image",
                    "score", "isToos");
        } else {
            if (StringUtils.isEmpty(query.getFields())) {
                query.setFields("id", "productId", "title", "brand", "isToos", listPrice, salePrice,
                        discountPercent, "url" + country, "reviewAverage", "reviews", "isPastSeason",
                        "freeGift" + catalog, "image", "isCloseout");
            }
        }

        query.setParam(GroupCollapseParams.GROUP_COLLAPSE, true);
        query.setParam(GroupCollapseParams.GROUP_COLLAPSE_FL,
                listPrice + "," + salePrice + "," + discountPercent + ",color,colorFamily");
    }

    private QueryResponse handleSpellCheck(SpellCheckResponse spellCheckResponse, T catalogSolrServer,
            SolrQuery query, String queryOp) throws SolrServerException {

        QueryResponse queryResponse;

        if (spellCheckResponse != null && StringUtils.isNotBlank(spellCheckResponse.getCollatedResult())) {
            //check if we have any spelling suggestion
            String tentativeCorrectedTerm = spellCheckResponse.getCollatedResult();

            //if we have spelling suggestions, try doing another search using 
            //q.op as the specified queryOp param (the default one is AND so we only add it if it's OR)
            //and use q="corrected phrase" to see if we can get results
            if ("OR".equals(queryOp)) {
                query.setParam("q.op", "OR");
                query.setParam("mm", getMinimumMatch());
            }
            query.setQuery(tentativeCorrectedTerm);
            queryResponse = catalogSolrServer.query(query);

            //if we didn't got any results from the search with q="corrected phrase" return null
            //otherwise return the results
            return isEmptySearch(queryResponse.getGroupResponse()) ? null : queryResponse;

        } else if ("OR".equals(queryOp)) {
            //for the match any terms scenario with no corrected terms do another query
            query.setParam("q.op", "OR");
            query.setParam("mm", getMinimumMatch());
            queryResponse = catalogSolrServer.query(query);
            return isEmptySearch(queryResponse.getGroupResponse()) ? null : queryResponse;
        } else {
            //if we didn't got any corrected terms and are not in the match any term scenario, 
            //then return null
            return null;
        }
    }

    protected boolean isEmptySearch(GroupResponse groupResponse) {
        boolean noResults = true;
        if (groupResponse != null) {
            for (GroupCommand command : groupResponse.getValues()) {
                if (command.getNGroups() > 0) {
                    noResults = false;
                    break;
                }
            }
        }
        return noResults;
    }

    @Override
    public QueryResponse query(SolrQuery solrQuery, String collection, Locale locale) throws SearchServerException {
        try {
            T server = getSolrServer(collection, locale);
            if (server == null) {
                throw create(SEARCH_EXCEPTION);
            }
            return server.query(solrQuery);
        } catch (SolrServerException ex) {
            throw create(SEARCH_EXCEPTION, ex);
        }
    }

    @Override
    public UpdateResponse add(Collection<SolrInputDocument> docs) throws SearchServerException {
        return add(docs, getCatalogCollection(), Locale.ENGLISH);
    }

    @Override
    public UpdateResponse add(Collection<SolrInputDocument> docs, Locale locale) throws SearchServerException {
        return add(docs, getCatalogCollection(), locale);
    }

    public UpdateResponse add(Collection<SolrInputDocument> docs, String collection) throws SearchServerException {
        return add(docs, collection, Locale.ENGLISH);
    }

    @Override
    public UpdateResponse add(Collection<SolrInputDocument> docs, String collection, Locale locale)
            throws SearchServerException {
        UpdateRequest req = new UpdateRequest();
        req.add(docs);
        req.setParam("collection", getCollectionName(collection, locale));

        try {
            return req.process(getSolrServer(collection, locale));
        } catch (SolrServerException ex) {
            throw create(UPDATE_EXCEPTION, ex);
        } catch (IOException ex) {
            throw create(UPDATE_EXCEPTION, ex);
        }
    }

    @Override
    public SolrPingResponse ping() throws SearchServerException {
        return ping(Locale.ENGLISH);
    }

    @Override
    public SolrPingResponse ping(Locale locale) throws SearchServerException {
        try {
            return getCatalogSolrServer(locale).ping();
        } catch (SolrServerException ex) {
            throw create(PING_EXCEPTION, ex);
        } catch (IOException ex) {
            throw create(PING_EXCEPTION, ex);
        }
    }

    @Override
    public UpdateResponse rollback() throws SearchServerException {
        return rollback(getCatalogCollection(), Locale.ENGLISH);
    }

    @Override
    public UpdateResponse rollback(Locale locale) throws SearchServerException {
        return rollback(getCatalogCollection(), locale);
    }

    public UpdateResponse rollback(String collection) throws SearchServerException {
        return rollback(collection, Locale.ENGLISH);
    }

    @Override
    public UpdateResponse rollback(String collection, Locale locale) throws SearchServerException {

        try {
            return getSolrServer(collection, locale).rollback();
        } catch (SolrServerException ex) {
            throw create(COMMIT_EXCEPTION, ex);
        } catch (IOException ex) {
            throw create(COMMIT_EXCEPTION, ex);
        }

    }

    @Override
    public UpdateResponse commit() throws SearchServerException {
        return commit(getCatalogCollection(), Locale.ENGLISH);
    }

    @Override
    public UpdateResponse commit(Locale locale) throws SearchServerException {
        return commit(getCatalogCollection(), locale);
    }

    public UpdateResponse commit(String collection) throws SearchServerException {
        return commit(collection, Locale.ENGLISH);
    }

    @Override
    public UpdateResponse commit(String collection, Locale locale) throws SearchServerException {

        try {
            return getSolrServer(collection, locale).commit();
        } catch (SolrServerException ex) {
            throw create(COMMIT_EXCEPTION, ex);
        } catch (IOException ex) {
            throw create(COMMIT_EXCEPTION, ex);
        }

    }

    @Override
    public UpdateResponse deleteByQuery(String query) throws SearchServerException {
        return deleteByQuery(query, getCatalogCollection());
    }

    @Override
    public UpdateResponse deleteByQuery(String query, Locale locale) throws SearchServerException {
        return deleteByQuery(query, getCatalogCollection(), locale);
    }

    public UpdateResponse deleteByQuery(String query, String collection) throws SearchServerException {
        return deleteByQuery(query, collection, Locale.ENGLISH);
    }

    @Override
    public UpdateResponse deleteByQuery(String query, String collection, Locale locale)
            throws SearchServerException {
        UpdateRequest req = new UpdateRequest();
        req.deleteByQuery(query);
        req.setParam("collection", getCollectionName(collection, locale));

        try {
            return req.process(getSolrServer(collection, locale));
        } catch (SolrServerException ex) {
            throw create(UPDATE_EXCEPTION, ex);
        } catch (IOException ex) {
            throw create(UPDATE_EXCEPTION, ex);
        }
    }

    @Override
    public NamedList<Object> analyze(DocumentAnalysisRequest request) throws SearchServerException {
        return analyze(request, Locale.ENGLISH);
    }

    @Override
    public NamedList<Object> analyze(DocumentAnalysisRequest request, Locale locale) throws SearchServerException {
        try {
            return getCatalogSolrServer(locale).request(request);
        } catch (SolrServerException ex) {
            throw create(ANALYSIS_EXCEPTION, ex);
        } catch (IOException ex) {
            throw create(ANALYSIS_EXCEPTION, ex);
        }
    }

    @Override
    public NamedList<Object> analyze(FieldAnalysisRequest request) throws SearchServerException {
        return analyze(request, Locale.ENGLISH);
    }

    @Override
    public NamedList<Object> analyze(FieldAnalysisRequest request, Locale locale) throws SearchServerException {
        try {
            return getCatalogSolrServer(locale).request(request);
        } catch (SolrServerException ex) {
            throw create(ANALYSIS_EXCEPTION, ex);
        } catch (IOException ex) {
            throw create(ANALYSIS_EXCEPTION, ex);
        }
    }

    @Override
    public NamedList<Object> analyze(FieldAnalysisRequest request, String collection, Locale locale)
            throws SearchServerException {
        try {
            T solrServer = getSolrServer(collection, locale);

            if (solrServer == null) {
                throw create(ANALYSIS_EXCEPTION);
            }

            return solrServer.request(request);
        } catch (SolrServerException ex) {
            throw create(ANALYSIS_EXCEPTION, ex);
        } catch (IOException ex) {
            throw create(ANALYSIS_EXCEPTION, ex);
        }
    }

    @Override
    public SearchResponse termVector(String query, String... fields) throws SearchServerException {
        return termVector(query, Locale.ENGLISH, fields);
    }

    @Override
    public SearchResponse termVector(String query, Locale locale, String... fields) throws SearchServerException {
        SolrQuery solrQuery = new SolrQuery(query);
        solrQuery.setRequestHandler("/tvrh");
        solrQuery.setFields(fields);
        solrQuery.setParam("tv.fl", "categoryName");

        try {
            QueryResponse queryResponse = getCatalogSolrServer(locale).query(solrQuery);
            return new SearchResponse(solrQuery, queryResponse, null, null, null, null, true);
        } catch (SolrServerException ex) {
            throw create(TERMS_EXCEPTION, ex);
        }
    }

    @Override
    public void onRepositoryItemChanged(String repositoryName, Set<String> itemDescriptorNames)
            throws RepositoryException, SearchServerException {
        if (repositoryName.endsWith(getSearchRepository().getRepositoryName())
                && (itemDescriptorNames.contains(SearchRepositoryItemDescriptor.SYNONYM)
                        || itemDescriptorNames.contains(SearchRepositoryItemDescriptor.SYNONYM_LIST))) {
            // TODO: support localized synonyms
            exportSynonyms(Locale.ENGLISH);
            reloadCollections();
        }
    }

    @Override
    public void onProductChanged(RepositoryItem product) throws RepositoryException, SearchServerException {
        throw new UnsupportedOperationException();
    }

    public void exportSynonyms() throws RepositoryException, SearchServerException {
        exportSynonyms(Locale.ENGLISH);
    }

    /**
     * Export the synonym lists in the search repository to Zoo Keeper. Each
     * synonym list is exported into its own file. When renaming a new list or
     * creating its synonyms won't have effect until its get configured in an
     * analyzer.
     *
     * When renaming a list that is currently being use by an analyzer it won't
     * be deleted to prevent the analyzer from breaking. However, new changes to
     * the renamed list won't take effect.
     *
     * @throws RepositoryException
     *             when an error occurs while retrieving synonyms from the
     *             repository
     * @throws ExportSynonymException
     *             if an error occurs while exporting the synonym list
     */
    public void exportSynonyms(Locale locale) throws RepositoryException, SearchServerException {
        RepositoryView view = searchRepository.getView(SearchRepositoryItemDescriptor.SYNONYM_LIST);
        RepositoryItem[] synonymLists = getSynonymListRql().executeQuery(view, null);
        if (synonymLists != null) {
            for (RepositoryItem synonymList : synonymLists) {
                exportSynonymList(synonymList, locale);
            }
        } else {
            if (isLoggingInfo()) {
                logInfo("No synonym lists were exported to ZooKeeper");
            }
        }
    }

    protected abstract void exportSynonymList(RepositoryItem synonymList, Locale locale)
            throws RepositoryException, SearchServerException;

    /**
     * Reloads the catalog and rule collections
     *
     * @throws SearchServerException if an error occurs while reloading the core
     */
    public void reloadCollections() throws SearchServerException {
        // @TODO add support to reload all locale cores
        String collectionName = getCatalogCollection(Locale.ENGLISH);
        reloadCollection(collectionName, Locale.ENGLISH);
        collectionName = getRulesCollection(Locale.ENGLISH);
        reloadCollection(collectionName, Locale.ENGLISH);
        collectionName = getAutocompleteCollection(Locale.ENGLISH);
        reloadCollection(collectionName, Locale.ENGLISH);
    }

    /**
     * Reloads the core
     *
     * @param collectionName
     *            the cored to be reloaded
     *
     * @throws SearchServerException if an error occurs while reloading the core
     * 
     */
    public abstract void reloadCollection(String collectionName, Locale locale) throws SearchServerException;

    private List<CategoryGraph> createCategoryGraph(SearchResponse searchResponse, String path, String catalogId,
            String categoryId, int depthLimit, String separator) {

        List<CategoryGraph> categoryGraphList = new ArrayList<CategoryGraph>();

        for (Facet facet : searchResponse.getFacets()) {
            if (CATEGORY_PATH.equalsIgnoreCase(facet.getName())) {
                searchResponse.removeFacet(facet.getName());
                return createCategoryGraphAux(facet, path, catalogId, categoryId, depthLimit, separator);
            }
        }
        return categoryGraphList;
    }

    private List<CategoryGraph> createCategoryGraphAux(Facet facet, String path, String catalogId,
            String categoryId, int depthLimit, String separator) {
        List<CategoryGraph> categoryGraphList = new ArrayList<CategoryGraph>();
        if (facet != null) {

            CategoryGraphBuilder categoryFacetBuilder = new CategoryGraphBuilder();

            boolean filterByDepth = depthLimit > 0 && StringUtils.isNotBlank(separator);

            // iterate through the flat category facet structure and create a
            // graph from it
            for (Filter filter : facet.getFilters()) {
                if (isLoggingDebug()) {
                    String filterPath = Utils.findFilterExpressionByName(filter.getFilterQueries(), CATEGORY_PATH);
                    logDebug("Generating CategoryGraph for path: " + filterPath);
                }
                if (filterByDepth && StringUtils.countMatches(filter.getName(), separator) > depthLimit) {
                    continue;
                }
                categoryFacetBuilder.addPath(filter);
            }

            if (StringUtils.isBlank(categoryId)) {
                // no category filtering scenario. Return top level list
                categoryGraphList = categoryFacetBuilder.getCategoryGraphList();
            } else {
                // category filtering scenario. Search hierarchy for the actual
                // result node.
                CategoryGraph currentLevelVO = categoryFacetBuilder.search(categoryId,
                        categoryFacetBuilder.getParentNode());
                if (currentLevelVO != null) {
                    categoryGraphList = currentLevelVO.getCategoryGraphNodes();
                } else {
                    if (isLoggingDebug()) {
                        logDebug("The CategoryGraph is empty for catalog: " + catalogId + " and category: "
                                + categoryId + " path: " + path + " This is expected for leaf categories");
                    }
                }
            }
        }

        return categoryGraphList;
    }
}