All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.couchbase.client.java.search.result.impl.DefaultAsyncSearchQueryResult Maven / Gradle / Ivy

/*
 * Copyright (c) 2016 Couchbase, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.couchbase.client.java.search.result.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.couchbase.client.core.CouchbaseException;
import com.couchbase.client.core.annotations.InterfaceAudience;
import com.couchbase.client.core.annotations.InterfaceStability;
import com.couchbase.client.java.document.json.JsonArray;
import com.couchbase.client.java.document.json.JsonObject;
import com.couchbase.client.java.error.FtsConsistencyTimeoutException;
import com.couchbase.client.java.error.FtsMalformedRequestException;
import com.couchbase.client.java.search.result.AsyncSearchQueryResult;
import com.couchbase.client.java.search.result.SearchMetrics;
import com.couchbase.client.java.search.result.SearchQueryRow;
import com.couchbase.client.java.search.result.SearchStatus;
import com.couchbase.client.java.search.result.facets.DateRange;
import com.couchbase.client.java.search.result.facets.DefaultDateRangeFacetResult;
import com.couchbase.client.java.search.result.facets.DefaultNumericRangeFacetResult;
import com.couchbase.client.java.search.result.facets.DefaultTermFacetResult;
import com.couchbase.client.java.search.result.facets.FacetResult;
import com.couchbase.client.java.search.result.facets.NumericRange;
import com.couchbase.client.java.search.result.facets.TermRange;
import com.couchbase.client.java.search.result.hits.DefaultHitLocations;
import com.couchbase.client.java.search.result.hits.HitLocations;
import rx.Observable;
import rx.exceptions.CompositeException;

/**
 * The default implementation for an {@link AsyncSearchQueryResult}
 *
 * @author Simon Baslé
 * @author Michael Nitschinger
 * @since 2.3.0
 */
@InterfaceStability.Experimental
@InterfaceAudience.Public
public class DefaultAsyncSearchQueryResult implements AsyncSearchQueryResult {

    private final SearchStatus status;
    private final Observable hits;
    private final Observable facets;
    private final Observable metrics;

    public DefaultAsyncSearchQueryResult(SearchStatus status,
            Observable hits, Observable facets,
            Observable metrics) {
        this.status = status;
        this.hits = hits;
        this.facets = facets;
        this.metrics = metrics;
    }

    @Override
    public SearchStatus status() {
        return status;
    }

    @Override
    public Observable hits() {
        return hits;
    }

    @Override
    public Observable facets() {
        return facets;
    }

    @Override
    public Observable metrics() {
        return metrics;
    }

    /**
     * Utility method to extract an {@link AsyncSearchQueryResult} from a JSON representation of the
     * whole search service response.
     *
     * @param json the whole response, as returned by the search service.
     * @return the corresponding {@link AsyncSearchQueryResult}.
     * @deprecated FTS is still in BETA so the response format is likely to change in a future version
     */
    @Deprecated
    public static AsyncSearchQueryResult fromJson(JsonObject json) {
        JsonObject jsonStatus = json.getObject("status");
        SearchStatus status = new DefaultSearchStatus(
                jsonStatus.getLong("total"),
                jsonStatus.getLong("failed"),
                jsonStatus.getLong("successful"));

        long totalHits = json.getLong("total_hits");
        long took = json.getLong("took");
        double maxScore = json.getDouble("max_score");
        SearchMetrics metrics = new DefaultSearchMetrics(took, totalHits, maxScore);

        List hits = new ArrayList();

        JsonArray rawHits = json.getArray("hits");
        if (rawHits != null) {
            for (Object rawHit : rawHits) {
                JsonObject hit = (JsonObject) rawHit;
                String index = hit.getString("index");
                String id = hit.getString("id");
                double score = hit.getDouble("score");
                JsonObject explanationJson = hit.getObject("explanation");
                if (explanationJson == null) {
                    explanationJson = JsonObject.empty();
                }

                HitLocations locations = DefaultHitLocations.from(hit.getObject("locations"));

                JsonObject fragmentsJson = hit.getObject("fragments");
                Map> fragments;
                if (fragmentsJson != null) {
                    fragments = new HashMap>(fragmentsJson.size());
                    for (String field : fragmentsJson.getNames()) {
                        List fragment;
                        JsonArray fragmentJson = fragmentsJson.getArray(field);
                        if (fragmentJson != null) {
                            fragment = new ArrayList(fragmentJson.size());
                            for (int i = 0; i < fragmentJson.size(); i++) {
                                fragment.add(fragmentJson.getString(i));
                            }
                        } else {
                            fragment = Collections.emptyList();
                        }
                        fragments.put(field, fragment);
                    }
                } else {
                    fragments = Collections.emptyMap();
                }

                Map fields;
                JsonObject fieldsJson = hit.getObject("fields");
                if (fieldsJson != null) {
                    fields = new HashMap(fieldsJson.size());
                    for (String f : fieldsJson.getNames()) {
                        fields.put(f, String.valueOf(fieldsJson.get(f)));
                    }
                } else {
                    fields = Collections.emptyMap();
                }

                hits.add(new DefaultSearchQueryRow(index, id, score, explanationJson, locations, fragments, fields));
            }
        }

        List facets;
        JsonObject facetsJson = json.getObject("facets");
        if (facetsJson != null) {
            facets = new ArrayList(facetsJson.size());
            for (String facetName : facetsJson.getNames()) {
                JsonObject facetJson = facetsJson.getObject(facetName);
                String field = facetJson.getString("field");
                long total = facetJson.getLong("total");
                long missing = facetJson.getLong("missing");
                long other = facetJson.getLong("other");

                if (facetJson.containsKey("numeric_ranges")) {
                    JsonArray rangesJson = facetJson.getArray("numeric_ranges");
                    List nr = new ArrayList(rangesJson.size());
                    for (Object o : rangesJson) {
                        JsonObject r = (JsonObject) o;
                        nr.add(new NumericRange(r.getString("name"), r.getDouble("min"), r.getDouble("max"), r.getLong("count")));
                    }
                    facets.add(new DefaultNumericRangeFacetResult(facetName, field, total, missing, other, nr));
                } else if (facetJson.containsKey("date_ranges")) {
                    JsonArray rangesJson = facetJson.getArray("date_ranges");
                    List dr = new ArrayList(rangesJson.size());
                    for (Object o : rangesJson) {
                        JsonObject r = (JsonObject) o;
                        dr.add(new DateRange(r.getString("name"), r.getString("start"), r.getString("end"),
                                r.getLong("count")));
                    }
                    facets.add(new DefaultDateRangeFacetResult(facetName, field, total, missing, other, dr));
                } else {
                    List tr;
                    JsonArray rangesJson = facetJson.getArray("terms");
                    if (rangesJson == null) {
                        tr = Collections.emptyList();
                    } else {
                        tr = new ArrayList(rangesJson.size());
                        for (Object o : rangesJson) {
                            JsonObject r = (JsonObject) o;
                            tr.add(new TermRange(r.getString("term"), r.getLong("count")));
                        }
                    }
                    facets.add(new DefaultTermFacetResult(facetName, field, total, missing, other, tr));
                }
            }
        } else {
            facets = Collections.emptyList();
        }

        Observable errors;
        Object errorsRaw = jsonStatus.get("errors");
        if (errorsRaw instanceof JsonArray) {
            JsonArray errorsJson = (JsonArray) errorsRaw;
            List exceptions = new ArrayList(errorsJson.size());
            for (Object o : errorsJson) {
                exceptions.add(new RuntimeException(String.valueOf(o)));
            }
            errors = Observable.error(new CompositeException(exceptions));
        } else if (errorsRaw instanceof JsonObject) {
            JsonObject errorsJson = (JsonObject) errorsRaw;
            List exceptions = new ArrayList(errorsJson.size());
            for (String key : errorsJson.getNames()) {
                exceptions.add(new RuntimeException(key + ": " + errorsJson.get(key)));
            }
            errors = Observable.error(new CompositeException(exceptions));
        } else {
            errors = Observable.empty();
        }

        return new DefaultAsyncSearchQueryResult(status,
                Observable.from(hits).concatWith(errors),
                Observable.from(facets),
                Observable.just(metrics));

    }

    /**
     * A utility method to convert an HTTP 400 response from the search service into a proper
     * {@link AsyncSearchQueryResult}. HTTP 400 indicates the request was malformed and couldn't
     * be parsed on the server. As of Couchbase Server 4.5 such a response is a text/plain
     * body that describes the parsing error. The whole body is emitted/thrown, wrapped in a
     * {@link FtsMalformedRequestException}.
     *
     * @param payload the HTTP 400 response body describing the parsing failure.
     * @return an {@link AsyncSearchQueryResult} that will emit a {@link FtsMalformedRequestException} when calling its
     * {@link AsyncSearchQueryResult#hits() hits()} method.
     * @deprecated FTS is still in BETA so the response format is likely to change in a future version, and be
     * unified with the HTTP 200 response format.
     */
    @Deprecated
    public static AsyncSearchQueryResult fromHttp400(String payload) {
        //dummy default values
        SearchStatus status = new DefaultSearchStatus(1L, 1L, 0L);
        SearchMetrics metrics = new DefaultSearchMetrics(0L, 0L, 0d);


        return new DefaultAsyncSearchQueryResult(
                status,
                Observable.error(new FtsMalformedRequestException(payload)),
                Observable.empty(),
                Observable.just(metrics)
        );
    }

    /**
     * A utility method to convert an HTTP 412 response from the search service into a proper
     * {@link AsyncSearchQueryResult}. HTTP 412 indicates the request couldn't be satisfied with given
     * consistency before the timeout expired. This is translated to a {@link FtsConsistencyTimeoutException}.
     *
     * @return an {@link AsyncSearchQueryResult} that will emit a {@link FtsConsistencyTimeoutException} when calling
     * its {@link AsyncSearchQueryResult#hits() hits()} method.
     * @deprecated FTS is still in BETA so the response format is likely to change in a future version, and be
     * unified with the HTTP 200 response format.
     */
    @Deprecated
    public static AsyncSearchQueryResult fromHttp412() {
        //dummy default values
        SearchStatus status = new DefaultSearchStatus(1L, 1L, 0L);
        SearchMetrics metrics = new DefaultSearchMetrics(0L, 0L, 0d);


        return new DefaultAsyncSearchQueryResult(
                status,
                Observable.error(new FtsConsistencyTimeoutException()),
                Observable.empty(),
                Observable.just(metrics)
        );
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy