org.alfresco.repo.search.impl.solr.SolrQueryHTTPClient.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.search.impl.solr.SolrQueryHTTPClient.java

Source

/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.search.impl.solr;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.http.HttpServletResponse;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.opencmis.dictionary.CMISStrictDictionaryService;
import org.alfresco.repo.admin.RepositoryState;
import org.alfresco.repo.domain.node.NodeDAO;
import org.alfresco.repo.index.shard.Floc;
import org.alfresco.repo.index.shard.ShardInstance;
import org.alfresco.repo.index.shard.ShardRegistry;
import org.alfresco.repo.search.impl.lucene.JSONResult;
import org.alfresco.repo.search.impl.lucene.LuceneQueryParserException;
import org.alfresco.repo.search.impl.lucene.SolrJSONResultSet;
import org.alfresco.repo.search.impl.lucene.SolrJsonProcessor;
import org.alfresco.repo.search.impl.lucene.SolrStatsResult;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.search.BasicSearchParameters;
import org.alfresco.service.cmr.search.LimitBy;
import org.alfresco.service.cmr.search.PermissionEvaluationMode;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.SearchParameters;
import org.alfresco.service.cmr.search.SearchParameters.FieldFacet;
import org.alfresco.service.cmr.search.SearchParameters.FieldFacetMethod;
import org.alfresco.service.cmr.search.SearchParameters.FieldFacetSort;
import org.alfresco.service.cmr.search.SearchParameters.SortDefinition;
import org.alfresco.service.cmr.search.StatsParameters;
import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.util.Pair;
import org.alfresco.util.ParameterCheck;
import org.alfresco.util.PropertyCheck;
import org.apache.commons.codec.net.URLCodec;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.extensions.surf.util.I18NUtil;

import com.hazelcast.impl.monitor.MapOperationsCounter;

/**
 * @author Andy
 */
public class SolrQueryHTTPClient implements BeanFactoryAware, InitializingBean {
    static Log s_logger = LogFactory.getLog(SolrQueryHTTPClient.class);

    private NodeService nodeService;

    private PermissionService permissionService;

    private NodeDAO nodeDAO;

    private TenantService tenantService;

    private ShardRegistry shardRegistry;

    private Map<String, String> languageMappings;

    private List<SolrStoreMapping> storeMappings;

    private HashMap<StoreRef, SolrStoreMappingWrapper> mappingLookup = new HashMap<StoreRef, SolrStoreMappingWrapper>();

    private String alternativeDictionary = CMISStrictDictionaryService.DEFAULT;

    private RepositoryState repositoryState;

    private BeanFactory beanFactory;

    private boolean includeGroupsForRoleAdmin = false;

    private int maximumResultsFromUnlimitedQuery = Integer.MAX_VALUE;

    private boolean anyDenyDenies;

    private boolean useDynamicShardRegistration = false;

    public static final int DEFAULT_SAVEPOST_BUFFER = 4096;

    private int defaultUnshardedFacetLimit = 100;

    private int defaultShardedFacetLimit = 20;

    public SolrQueryHTTPClient() {
    }

    public void init() {
        PropertyCheck.mandatory(this, "NodeService", nodeService);
        PropertyCheck.mandatory(this, "PermissionService", permissionService);
        PropertyCheck.mandatory(this, "TenantService", tenantService);
        PropertyCheck.mandatory(this, "LanguageMappings", languageMappings);
        PropertyCheck.mandatory(this, "StoreMappings", storeMappings);
        PropertyCheck.mandatory(this, "RepositoryState", repositoryState);

    }

    public void setAlternativeDictionary(String alternativeDictionary) {
        this.alternativeDictionary = alternativeDictionary;
    }

    /**
     * @param repositoryState the repositoryState to set
     */
    public void setRepositoryState(RepositoryState repositoryState) {
        this.repositoryState = repositoryState;
    }

    public void setNodeService(NodeService nodeService) {
        this.nodeService = nodeService;
    }

    /**
     * @param nodeDAO the nodeDao to set
     */
    public void setNodeDAO(NodeDAO nodeDAO) {
        this.nodeDAO = nodeDAO;
    }

    public void setPermissionService(PermissionService permissionService) {
        this.permissionService = permissionService;
    }

    public void setTenantService(TenantService tenantService) {
        this.tenantService = tenantService;
    }

    public void setShardRegistry(ShardRegistry shardRegistry) {
        this.shardRegistry = shardRegistry;
    }

    public void setUseDynamicShardRegistration(boolean useDynamicShardRegistration) {
        this.useDynamicShardRegistration = useDynamicShardRegistration;
    }

    public void setLanguageMappings(Map<String, String> languageMappings) {
        this.languageMappings = languageMappings;
    }

    public void setStoreMappings(List storeMappings) {
        this.storeMappings = storeMappings;
    }

    /**
      * @param includeGroupsForRoleAdmin the includeGroupsForRoleAdmin to set
      */
    public void setIncludeGroupsForRoleAdmin(boolean includeGroupsForRoleAdmin) {
        this.includeGroupsForRoleAdmin = includeGroupsForRoleAdmin;
    }

    /**
     * @param maximumResultsFromUnlimitedQuery
     *            the maximum number of results to request from an otherwise unlimited query
     */
    public void setMaximumResultsFromUnlimitedQuery(int maximumResultsFromUnlimitedQuery) {
        this.maximumResultsFromUnlimitedQuery = maximumResultsFromUnlimitedQuery;
    }

    /**
     * When set, a single DENIED ACL entry for any authority will result in
     * access being denied as a whole. See system property {@code security.anyDenyDenies}
     * 
     * @param anyDenyDenies boolean
     */
    public void setAnyDenyDenies(boolean anyDenyDenies) {
        this.anyDenyDenies = anyDenyDenies;
    }

    /**
     * @param defaultUnshardedFacetLimit the defaultUnshardedFacetLimit to set
     */
    public void setDefaultUnshardedFacetLimit(int defaultUnshardedFacetLimit) {
        this.defaultUnshardedFacetLimit = defaultUnshardedFacetLimit;
    }

    /**
     * @param defaultShardedFacetLimit the defaultShardedFacetLimit to set
     */
    public void setDefaultShardedFacetLimit(int defaultShardedFacetLimit) {
        this.defaultShardedFacetLimit = defaultShardedFacetLimit;
    }

    /**
     * Executes a solr query for statistics
     * 
     * @param searchParameters StatsParameters
     * @return SolrStatsResult
     */
    public SolrStatsResult executeStatsQuery(final StatsParameters searchParameters) {
        if (repositoryState.isBootstrapping()) {
            throw new AlfrescoRuntimeException(
                    "SOLR stats queries can not be executed while the repository is bootstrapping");
        }

        try {
            StoreRef store = extractStoreRef(searchParameters);
            SolrStoreMappingWrapper mapping = extractMapping(store);
            Locale locale = extractLocale(searchParameters);

            Pair<HttpClient, String> httpClientAndBaseUrl = mapping.getHttpClientAndBaseUrl();
            HttpClient httpClient = httpClientAndBaseUrl.getFirst();
            String url = buildStatsUrl(searchParameters, httpClientAndBaseUrl.getSecond(), locale, mapping);
            JSONObject body = buildStatsBody(searchParameters, tenantService.getCurrentUserDomain(), locale);

            if (httpClient == null) {
                throw new AlfrescoRuntimeException("No http client for store " + store.toString());
            }

            return (SolrStatsResult) postSolrQuery(httpClient, url, body, new SolrJsonProcessor<SolrStatsResult>() {

                @Override
                public SolrStatsResult getResult(JSONObject json) {
                    return new SolrStatsResult(json, searchParameters.isDateSearch());
                }

            });

        } catch (UnsupportedEncodingException e) {
            throw new LuceneQueryParserException("stats", e);
        } catch (HttpException e) {
            throw new LuceneQueryParserException("stats", e);
        } catch (IOException e) {
            throw new LuceneQueryParserException("stats", e);
        } catch (JSONException e) {
            throw new LuceneQueryParserException("stats", e);
        }
    }

    protected String buildStatsUrl(StatsParameters searchParameters, String baseUrl, Locale locale,
            SolrStoreMappingWrapper mapping) throws UnsupportedEncodingException {
        URLCodec encoder = new URLCodec();
        StringBuilder url = new StringBuilder();
        String languageUrlFragment = extractLanguageFragment(searchParameters.getLanguage());

        url.append(baseUrl);
        url.append("/").append(languageUrlFragment);
        url.append("?wt=").append(encoder.encode("json", "UTF-8"));
        url.append("&locale=").append(encoder.encode(locale.toString(), "UTF-8"));

        url.append(buildSortParameters(searchParameters, encoder));

        url.append("&stats=true");
        url.append("&rows=0");
        if (!StringUtils.isBlank(searchParameters.getFilterQuery())) {
            url.append("?fq=").append(encoder.encode(searchParameters.getFilterQuery(), "UTF-8"));
        }

        for (Entry<String, String> entry : searchParameters.getStatsParameters().entrySet()) {
            url.append("&stats.").append(entry.getKey()).append("=")
                    .append(encoder.encode(entry.getValue(), "UTF-8"));
        }

        if ((mapping != null) && ((searchParameters.getStores().size() > 1) || (mapping.isSharded()))) {
            boolean requiresSeparator = false;
            url.append("&shards=");
            for (StoreRef storeRef : searchParameters.getStores()) {
                SolrStoreMappingWrapper storeMapping = extractMapping(storeRef);

                if (requiresSeparator) {
                    url.append(',');
                } else {
                    requiresSeparator = true;
                }

                url.append(storeMapping.getShards());

            }
        }

        return url.toString();
    }

    protected JSONObject buildStatsBody(StatsParameters searchParameters, String tenant, Locale locale)
            throws JSONException {
        JSONObject body = new JSONObject();
        body.put("query", searchParameters.getQuery());

        JSONArray tenants = new JSONArray();
        tenants.put(tenant);
        body.put("tenants", tenants);

        JSONArray locales = new JSONArray();
        locales.put(locale);
        body.put("locales", locales);

        return body;
    }

    public ResultSet executeQuery(final SearchParameters searchParameters, String language) {
        if (repositoryState.isBootstrapping()) {
            throw new AlfrescoRuntimeException(
                    "SOLR queries can not be executed while the repository is bootstrapping");
        }

        try {
            StoreRef store = extractStoreRef(searchParameters);
            SolrStoreMappingWrapper mapping = extractMapping(store);
            Locale locale = extractLocale(searchParameters);

            URLCodec encoder = new URLCodec();
            StringBuilder url = new StringBuilder();

            Pair<HttpClient, String> httpClientAndBaseUrl = mapping.getHttpClientAndBaseUrl();
            HttpClient httpClient = httpClientAndBaseUrl.getFirst();

            url.append(httpClientAndBaseUrl.getSecond());

            String languageUrlFragment = extractLanguageFragment(language);
            if (!url.toString().endsWith("/")) {
                url.append("/");
            }
            url.append(languageUrlFragment);

            // Send the query in JSON only
            // url.append("?q=");
            // url.append(encoder.encode(searchParameters.getQuery(), "UTF-8"));
            url.append("?wt=").append(encoder.encode("json", "UTF-8"));
            url.append("&fl=").append(encoder.encode("DBID,score", "UTF-8"));

            if ((searchParameters.getStores().size() > 1) || (mapping.isSharded())) {
                boolean requiresSeparator = false;
                url.append("&shards=");
                for (StoreRef storeRef : searchParameters.getStores()) {
                    SolrStoreMappingWrapper storeMapping = extractMapping(storeRef);

                    if (requiresSeparator) {
                        url.append(',');
                    } else {
                        requiresSeparator = true;
                    }

                    url.append(storeMapping.getShards());

                }
            }

            // Emulate old limiting behaviour and metadata
            final LimitBy limitBy;
            int maxResults = -1;
            if (searchParameters.getMaxItems() >= 0) {
                maxResults = searchParameters.getMaxItems();
                limitBy = LimitBy.FINAL_SIZE;
            } else if (searchParameters.getLimitBy() == LimitBy.FINAL_SIZE && searchParameters.getLimit() >= 0) {
                maxResults = searchParameters.getLimit();
                limitBy = LimitBy.FINAL_SIZE;
            } else {
                maxResults = searchParameters.getMaxPermissionChecks();
                if (maxResults < 0) {
                    maxResults = maximumResultsFromUnlimitedQuery;
                }
                limitBy = LimitBy.NUMBER_OF_PERMISSION_EVALUATIONS;
            }
            url.append("&rows=").append(String.valueOf(maxResults));

            url.append("&df=").append(encoder.encode(searchParameters.getDefaultFieldName(), "UTF-8"));
            url.append("&start=").append(encoder.encode("" + searchParameters.getSkipCount(), "UTF-8"));

            url.append("&locale=");
            url.append(encoder.encode(locale.toString(), "UTF-8"));
            url.append("&").append(SearchParameters.ALTERNATIVE_DICTIONARY).append("=")
                    .append(alternativeDictionary);
            for (String paramName : searchParameters.getExtraParameters().keySet()) {
                url.append("&").append(paramName).append("=")
                        .append(searchParameters.getExtraParameters().get(paramName));
            }
            StringBuffer sortBuffer = buildSortParameters(searchParameters, encoder);
            url.append(sortBuffer);

            if (searchParameters.getPermissionEvaluation() != PermissionEvaluationMode.NONE) {
                url.append("&fq=").append(encoder.encode("{!afts}AUTHORITY_FILTER_FROM_JSON", "UTF-8"));
            }

            if (searchParameters.getExcludeTenantFilter() == false) {
                url.append("&fq=").append(encoder.encode("{!afts}TENANT_FILTER_FROM_JSON", "UTF-8"));
            }

            if (searchParameters.getFieldFacets().size() > 0 || searchParameters.getFacetQueries().size() > 0) {
                url.append("&facet=").append(encoder.encode("true", "UTF-8"));
                for (FieldFacet facet : searchParameters.getFieldFacets()) {
                    url.append("&facet.field=").append(encoder.encode(facet.getField(), "UTF-8"));
                    if (facet.getEnumMethodCacheMinDF() != 0) {
                        url.append("&")
                                .append(encoder.encode("f." + facet.getField() + ".facet.enum.cache.minDf",
                                        "UTF-8"))
                                .append("=").append(encoder.encode("" + facet.getEnumMethodCacheMinDF(), "UTF-8"));
                    }
                    int facetLimit;
                    if (facet.getLimitOrNull() == null) {
                        if (mapping.isSharded()) {
                            facetLimit = defaultShardedFacetLimit;
                        } else {
                            facetLimit = defaultUnshardedFacetLimit;
                        }
                    } else {
                        facetLimit = facet.getLimitOrNull().intValue();
                    }
                    url.append("&").append(encoder.encode("f." + facet.getField() + ".facet.limit", "UTF-8"))
                            .append("=").append(encoder.encode("" + facetLimit, "UTF-8"));
                    if (facet.getMethod() != null) {
                        url.append("&").append(encoder.encode("f." + facet.getField() + ".facet.method", "UTF-8"))
                                .append("=").append(encoder.encode(
                                        facet.getMethod() == FieldFacetMethod.ENUM ? "enum" : "fc", "UTF-8"));
                    }
                    if (facet.getMinCount() != 0) {
                        url.append("&").append(encoder.encode("f." + facet.getField() + ".facet.mincount", "UTF-8"))
                                .append("=").append(encoder.encode("" + facet.getMinCount(), "UTF-8"));
                    }
                    if (facet.getOffset() != 0) {
                        url.append("&").append(encoder.encode("f." + facet.getField() + ".facet.offset", "UTF-8"))
                                .append("=").append(encoder.encode("" + facet.getOffset(), "UTF-8"));
                    }
                    if (facet.getPrefix() != null) {
                        url.append("&").append(encoder.encode("f." + facet.getField() + ".facet.prefix", "UTF-8"))
                                .append("=").append(encoder.encode("" + facet.getPrefix(), "UTF-8"));
                    }
                    if (facet.getSort() != null) {
                        url.append("&").append(encoder.encode("f." + facet.getField() + ".facet.sort", "UTF-8"))
                                .append("=").append(encoder.encode(
                                        facet.getSort() == FieldFacetSort.COUNT ? "count" : "index", "UTF-8"));
                    }

                }
                for (String facetQuery : searchParameters.getFacetQueries()) {
                    if (!facetQuery.startsWith("{!afts")) {
                        facetQuery = "{!afts}" + facetQuery;
                    }
                    url.append("&facet.query=").append(encoder.encode(facetQuery, "UTF-8"));
                }
            }

            // filter queries
            for (String filterQuery : searchParameters.getFilterQueries()) {
                if (!filterQuery.startsWith("{!afts")) {
                    filterQuery = "{!afts}" + filterQuery;
                }
                url.append("&fq=").append(encoder.encode(filterQuery, "UTF-8"));
            }

            // end of field facets

            final String searchTerm = searchParameters.getSearchTerm();
            String spellCheckQueryStr = null;
            if (searchTerm != null && searchParameters.isSpellCheck()) {
                StringBuilder builder = new StringBuilder();
                builder.append("&spellcheck.q=").append(encoder.encode(searchTerm, "UTF-8"));
                builder.append("&spellcheck=").append(encoder.encode("true", "UTF-8"));
                spellCheckQueryStr = builder.toString();
                url.append(spellCheckQueryStr);
            }

            JSONObject body = new JSONObject();
            body.put("query", searchParameters.getQuery());

            // Authorities go over as is - and tenant mangling and query building takes place on the SOLR side

            Set<String> allAuthorisations = permissionService.getAuthorisations();
            boolean includeGroups = includeGroupsForRoleAdmin ? true
                    : !allAuthorisations.contains(PermissionService.ADMINISTRATOR_AUTHORITY);

            JSONArray authorities = new JSONArray();
            for (String authority : allAuthorisations) {
                if (includeGroups) {
                    authorities.put(authority);
                } else {
                    if (AuthorityType.getAuthorityType(authority) != AuthorityType.GROUP) {
                        authorities.put(authority);
                    }
                }
            }
            body.put("authorities", authorities);
            body.put("anyDenyDenies", anyDenyDenies);

            JSONArray tenants = new JSONArray();
            tenants.put(tenantService.getCurrentUserDomain());
            body.put("tenants", tenants);

            JSONArray locales = new JSONArray();
            for (Locale currentLocale : searchParameters.getLocales()) {
                locales.put(DefaultTypeConverter.INSTANCE.convert(String.class, currentLocale));
            }
            if (locales.length() == 0) {
                locales.put(I18NUtil.getLocale());
            }
            body.put("locales", locales);

            JSONArray templates = new JSONArray();
            for (String templateName : searchParameters.getQueryTemplates().keySet()) {
                JSONObject template = new JSONObject();
                template.put("name", templateName);
                template.put("template", searchParameters.getQueryTemplates().get(templateName));
                templates.put(template);
            }
            body.put("templates", templates);

            JSONArray allAttributes = new JSONArray();
            for (String attribute : searchParameters.getAllAttributes()) {
                allAttributes.put(attribute);
            }
            body.put("allAttributes", allAttributes);

            body.put("defaultFTSOperator", searchParameters.getDefaultFTSOperator());
            body.put("defaultFTSFieldOperator", searchParameters.getDefaultFTSFieldOperator());
            body.put("queryConsistency", searchParameters.getQueryConsistency());
            if (searchParameters.getMlAnalaysisMode() != null) {
                body.put("mlAnalaysisMode", searchParameters.getMlAnalaysisMode().toString());
            }
            body.put("defaultNamespace", searchParameters.getNamespace());

            JSONArray textAttributes = new JSONArray();
            for (String attribute : searchParameters.getTextAttributes()) {
                textAttributes.put(attribute);
            }
            body.put("textAttributes", textAttributes);

            final int maximumResults = maxResults; //just needed for the final parameter

            return (ResultSet) postSolrQuery(httpClient, url.toString(), body,
                    new SolrJsonProcessor<SolrJSONResultSet>() {

                        @Override
                        public SolrJSONResultSet getResult(JSONObject json) {
                            return new SolrJSONResultSet(json, searchParameters, nodeService, nodeDAO, limitBy,
                                    maximumResults);
                        }

                    }, spellCheckQueryStr);
        } catch (UnsupportedEncodingException e) {
            throw new LuceneQueryParserException("", e);
        } catch (HttpException e) {
            throw new LuceneQueryParserException("", e);
        } catch (IOException e) {
            throw new LuceneQueryParserException("", e);
        } catch (JSONException e) {
            throw new LuceneQueryParserException("", e);
        }
    }

    protected JSONResult postSolrQuery(HttpClient httpClient, String url, JSONObject body,
            SolrJsonProcessor<?> jsonProcessor)
            throws UnsupportedEncodingException, IOException, HttpException, URIException, JSONException {
        return postSolrQuery(httpClient, url, body, jsonProcessor, null);
    }

    protected JSONResult postSolrQuery(HttpClient httpClient, String url, JSONObject body,
            SolrJsonProcessor<?> jsonProcessor, String spellCheckParams)
            throws UnsupportedEncodingException, IOException, HttpException, URIException, JSONException {
        JSONObject json = postQuery(httpClient, url, body);
        if (spellCheckParams != null) {
            SpellCheckDecisionManager manager = new SpellCheckDecisionManager(json, url, body, spellCheckParams);
            if (manager.isCollate()) {
                json = postQuery(httpClient, manager.getUrl(), body);
            }
            json.put("spellcheck", manager.getSpellCheckJsonValue());
        }

        JSONResult results = jsonProcessor.getResult(json);

        if (s_logger.isDebugEnabled()) {
            s_logger.debug("Sent :" + url);
            s_logger.debug("   with: " + body.toString());
            s_logger.debug("Got: " + results.getNumberFound() + " in " + results.getQueryTime() + " ms");
        }

        return results;
    }

    protected JSONObject postQuery(HttpClient httpClient, String url, JSONObject body)
            throws UnsupportedEncodingException, IOException, HttpException, URIException, JSONException {
        PostMethod post = new PostMethod(url);
        if (body.toString().length() > DEFAULT_SAVEPOST_BUFFER) {
            post.getParams().setBooleanParameter(HttpMethodParams.USE_EXPECT_CONTINUE, true);
        }
        post.setRequestEntity(new ByteArrayRequestEntity(body.toString().getBytes("UTF-8"), "application/json"));

        try {
            httpClient.executeMethod(post);

            if (post.getStatusCode() == HttpStatus.SC_MOVED_PERMANENTLY
                    || post.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY) {
                Header locationHeader = post.getResponseHeader("location");
                if (locationHeader != null) {
                    String redirectLocation = locationHeader.getValue();
                    post.setURI(new URI(redirectLocation, true));
                    httpClient.executeMethod(post);
                }
            }

            if (post.getStatusCode() != HttpServletResponse.SC_OK) {
                throw new LuceneQueryParserException(
                        "Request failed " + post.getStatusCode() + " " + url.toString());
            }

            Reader reader = new BufferedReader(
                    new InputStreamReader(post.getResponseBodyAsStream(), post.getResponseCharSet()));
            // TODO - replace with streaming-based solution e.g. SimpleJSON ContentHandler
            JSONObject json = new JSONObject(new JSONTokener(reader));

            if (json.has("status")) {
                JSONObject status = json.getJSONObject("status");
                if (status.getInt("code") != HttpServletResponse.SC_OK) {
                    throw new LuceneQueryParserException("SOLR side error: " + status.getString("message"));
                }
            }
            return json;
        } finally {
            post.releaseConnection();
        }
    }

    private StringBuffer buildSortParameters(BasicSearchParameters searchParameters, URLCodec encoder)
            throws UnsupportedEncodingException {
        StringBuffer sortBuffer = new StringBuffer();
        for (SortDefinition sortDefinition : searchParameters.getSortDefinitions()) {
            if (sortBuffer.length() == 0) {
                sortBuffer.append("&sort=");
            } else {
                sortBuffer.append(encoder.encode(", ", "UTF-8"));
            }
            // MNT-8557 fix, manually replace ' ' with '%20'
            // The sort can be different, see MNT-13742
            switch (sortDefinition.getSortType()) {
            case DOCUMENT:
                sortBuffer.append(encoder.encode("_docid_", "UTF-8")).append(encoder.encode(" ", "UTF-8"));
                break;
            case SCORE:
                sortBuffer.append(encoder.encode("score", "UTF-8")).append(encoder.encode(" ", "UTF-8"));
                break;
            case FIELD:
            default:
                sortBuffer.append(encoder.encode(sortDefinition.getField().replaceAll(" ", "%20"), "UTF-8"))
                        .append(encoder.encode(" ", "UTF-8"));
                break;
            }
            if (sortDefinition.isAscending()) {
                sortBuffer.append(encoder.encode("asc", "UTF-8"));
            } else {
                sortBuffer.append(encoder.encode("desc", "UTF-8"));
            }

        }
        return sortBuffer;
    }

    private Locale extractLocale(BasicSearchParameters searchParameters) {
        Locale locale = I18NUtil.getLocale();
        if (searchParameters.getLocales().size() > 0) {
            locale = searchParameters.getLocales().get(0);
        }
        return locale;
    }

    private String extractLanguageFragment(String language) {
        String languageUrlFragment = languageMappings.get(language);
        if (languageUrlFragment == null) {
            throw new AlfrescoRuntimeException("No solr query support for language " + language);
        }
        return languageUrlFragment;
    }

    private SolrStoreMappingWrapper extractMapping(StoreRef store) {
        if ((shardRegistry != null) && useDynamicShardRegistration) {
            SearchParameters sp = new SearchParameters();
            sp.addStore(store);
            List<ShardInstance> slice = shardRegistry.getIndexSlice(sp);
            if ((slice == null) || (slice.size() == 0)) {
                s_logger.error("No available shards for solr query of store " + store
                        + " - trying non-dynamic configuration");
                SolrStoreMappingWrapper mappings = mappingLookup.get(store);
                if (mappings == null) {
                    throw new LuceneQueryParserException("No solr query support for store " + store);
                }
                return mappings;
            }
            return DynamicSolrStoreMappingWrapperFactory.wrap(slice, beanFactory);
        } else {
            SolrStoreMappingWrapper mappings = mappingLookup.get(store);

            if (mappings == null) {
                throw new LuceneQueryParserException("No solr query support for store " + store);
            }
            return mappings;
        }
    }

    private StoreRef extractStoreRef(BasicSearchParameters searchParameters) {
        if (searchParameters.getStores().size() == 0) {
            throw new AlfrescoRuntimeException("No store for query");
        }

        StoreRef store = searchParameters.getStores().get(0);
        return store;
    }

    /* (non-Javadoc)
     * @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
     */
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    /* (non-Javadoc)
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        mappingLookup.clear();
        for (SolrStoreMapping mapping : storeMappings) {
            mappingLookup.put(mapping.getStoreRef(), new ExplicitSolrStoreMappingWrapper(mapping, beanFactory));
        }
    }

    /**
     * @param storeRef
     * @param handler
     * @param params
     * @return
     */
    public JSONObject execute(StoreRef storeRef, String handler, HashMap<String, String> params) {
        try {
            SolrStoreMappingWrapper mapping = extractMapping(storeRef);

            URLCodec encoder = new URLCodec();
            StringBuilder url = new StringBuilder();

            Pair<HttpClient, String> httpClientAndBaseUrl = mapping.getHttpClientAndBaseUrl();
            HttpClient httpClient = httpClientAndBaseUrl.getFirst();

            for (String key : params.keySet()) {
                String value = params.get(key);
                if (url.length() == 0) {
                    url.append(httpClientAndBaseUrl.getSecond());

                    if (!handler.startsWith("/")) {
                        url.append("/");
                    }
                    url.append(handler);
                    url.append("?");
                    url.append(encoder.encode(key, "UTF-8"));
                    url.append("=");
                    url.append(encoder.encode(value, "UTF-8"));
                } else {
                    url.append("&");
                    url.append(encoder.encode(key, "UTF-8"));
                    url.append("=");
                    url.append(encoder.encode(value, "UTF-8"));
                }

            }

            if (mapping.isSharded()) {
                url.append("&shards=");
                url.append(mapping.getShards());
            }

            // PostMethod post = new PostMethod(url.toString());
            GetMethod get = new GetMethod(url.toString());

            try {
                httpClient.executeMethod(get);

                if (get.getStatusCode() == HttpStatus.SC_MOVED_PERMANENTLY
                        || get.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY) {
                    Header locationHeader = get.getResponseHeader("location");
                    if (locationHeader != null) {
                        String redirectLocation = locationHeader.getValue();
                        get.setURI(new URI(redirectLocation, true));
                        httpClient.executeMethod(get);
                    }
                }

                if (get.getStatusCode() != HttpServletResponse.SC_OK) {
                    throw new LuceneQueryParserException(
                            "Request failed " + get.getStatusCode() + " " + url.toString());
                }

                Reader reader = new BufferedReader(new InputStreamReader(get.getResponseBodyAsStream()));
                // TODO - replace with streaming-based solution e.g. SimpleJSON ContentHandler
                JSONObject json = new JSONObject(new JSONTokener(reader));
                return json;
            } finally {
                get.releaseConnection();
            }
        } catch (UnsupportedEncodingException e) {
            throw new LuceneQueryParserException("", e);
        } catch (HttpException e) {
            throw new LuceneQueryParserException("", e);
        } catch (IOException e) {
            throw new LuceneQueryParserException("", e);
        } catch (JSONException e) {
            throw new LuceneQueryParserException("", e);
        }
    }

    /**
     * @return
     */
    public boolean isSharded() {
        if ((shardRegistry != null) && useDynamicShardRegistration) {
            for (Floc floc : shardRegistry.getFlocs().keySet()) {
                if (floc.getNumberOfShards() > 1) {
                    return true;
                }
            }
            return false;

        } else {
            for (SolrStoreMappingWrapper mapping : mappingLookup.values()) {
                if (mapping.isSharded()) {
                    return true;
                }
            }
            return false;
        }

    }
}