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

com.couchbase.client.java.util.rawQuerying.AsyncRawQueryExecutor 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.util.rawQuerying;

import java.io.IOException;

import com.couchbase.client.core.ClusterFacade;
import com.couchbase.client.core.CouchbaseException;
import com.couchbase.client.core.annotations.InterfaceAudience;
import com.couchbase.client.core.annotations.InterfaceStability;
import com.couchbase.client.core.logging.CouchbaseLogger;
import com.couchbase.client.core.logging.CouchbaseLoggerFactory;
import com.couchbase.client.core.message.ResponseStatus;
import com.couchbase.client.core.message.query.RawQueryRequest;
import com.couchbase.client.core.message.query.RawQueryResponse;
import com.couchbase.client.core.message.search.SearchQueryRequest;
import com.couchbase.client.core.message.search.SearchQueryResponse;
import com.couchbase.client.deps.io.netty.buffer.ByteBuf;
import com.couchbase.client.deps.io.netty.util.CharsetUtil;
import com.couchbase.client.deps.io.netty.util.ReferenceCountUtil;
import com.couchbase.client.java.AsyncBucket;
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.error.QueryExecutionException;
import com.couchbase.client.java.error.TranscodingException;
import com.couchbase.client.java.query.N1qlQuery;
import com.couchbase.client.java.search.SearchQuery;
import com.couchbase.client.java.search.queries.AbstractFtsQuery;
import com.couchbase.client.java.transcoder.JacksonTransformers;
import com.couchbase.client.java.transcoder.TranscoderUtils;
import rx.Observable;
import rx.functions.Func0;
import rx.functions.Func1;

/**
 * A utility class that allows to perform {@link N1qlQuery N1QL} and {@link SearchQuery FTS} queries
 * asynchronously and receive a raw version of the service's JSON response.
 *
 * The responses can directly be exposed as {@link JsonObject} or {@link String}, but custom methods allow
 * to work from a byte array (for N1QL) or String (for FTS) and perform a custom deserialization.
 *
 * Note that this class is outside of the Bucket API as it is {@link InterfaceStability.Uncommitted Uncommitted},
 * and is not common to all Couchbase SDKs.
 *
 * @author Simon Baslé
 * @since 2.3
 */
@InterfaceStability.Uncommitted
@InterfaceAudience.Public
public class AsyncRawQueryExecutor {

    private static final CouchbaseLogger LOGGER = CouchbaseLoggerFactory.getInstance(AsyncRawQueryExecutor.class);

    private final String bucket;
    private final String password;
    private final ClusterFacade core;

    public AsyncRawQueryExecutor(String bucket, String password, ClusterFacade core) {
        this.bucket = bucket;
        this.password = password;
        this.core = core;
    }

    /**
     * Asynchronously perform a {@link N1qlQuery} and return the raw N1QL response as a {@link JsonObject}.
     * Note that the query is executed "as is", without any processing comparable to what is done in
     * {@link AsyncBucket#query(N1qlQuery)} (like enforcing a server side timeout or managing prepared
     * statements).
     *
     * @param query the query to execute.
     * @return an {@link Observable} of the N1QL response as a {@link JsonObject}.
     */
    public Observable n1qlToJsonObject(N1qlQuery query) {
        return n1qlToRawCustom(query, new Func1() {
            @Override
            public JsonObject call(TranscoderUtils.ByteBufToArray converted) {
                try {
                    return JacksonTransformers.MAPPER.readValue(converted.byteArray, converted.offset, converted.length, JsonObject.class);
                } catch (IOException e) {
                    throw new TranscodingException("Unable to deserialize the N1QL raw response", e);
                }
            }
        });
    }

    /**
     * Asynchronously perform a {@link N1qlQuery} and return the raw N1QL response as a String.
     * Note that the query is executed "as is", without any processing comparable to what is done in
     * {@link AsyncBucket#query(N1qlQuery)} (like enforcing a server side timeout or managing prepared
     * statements).
     *
     * @param query the query to execute.
     * @return an {@link Observable} of the N1QL response as a String.
     */
    public Observable n1qlToRawJson(N1qlQuery query) {
        return n1qlToRawCustom(query, new Func1() {
            @Override
            public String call(TranscoderUtils.ByteBufToArray converted) {
                return new String(converted.byteArray, converted.offset, converted.length, CharsetUtil.UTF_8);
            }
        });
    }

    /**
     * Asynchronously perform a {@link N1qlQuery} and apply a user function to deserialize the raw N1QL
     * response, which is represented as a {@link TranscoderUtils.ByteBufToArray}.
     *
     * The array is derived from a {@link ByteBuf} that will be released, so it shouldn't be used
     * to back the returned instance. Its scope should be considered the scope of the call method.
     *
     * Note that the query is executed "as is", without any processing comparable to what is done in
     * {@link AsyncBucket#query(N1qlQuery)} (like enforcing a server side timeout or managing prepared
     * statements).
     *
     * @param query the query to execute.
     * @param deserializer a deserializer function that transforms the byte representation of the response into a custom type T.
     * @param  the type of the response, once deserialized by the user-provided function.
     * @return an {@link Observable} of the N1QL response as a T.
     */
    public  Observable n1qlToRawCustom(final N1qlQuery query, final Func1 deserializer) {
        return Observable.defer(new Func0>() {
            @Override
            public Observable call() {
                RawQueryRequest request = RawQueryRequest.jsonQuery(query.n1ql().toString(), bucket, password);
                return core.send(request);
            }
        }).map(new Func1() {
            @Override
            public T call(RawQueryResponse response) {
                try {
                    if (response.httpStatusCode() == 200) {
                        return deserializer.call(TranscoderUtils.byteBufToByteArray(response.jsonResponse()));
                    }
                    LOGGER.debug("Unable to perform raw N1QL query (see exception), body was: " +
                            response.jsonResponse().toString( CharsetUtil.UTF_8));
                    throw new QueryExecutionException("Unable to perform raw N1QL query: " + response.httpStatusCode() +
                            " - " + response.httpStatusMsg(), JsonObject.empty());
                } finally {
                    ReferenceCountUtil.release(response.jsonResponse());
                }
            }
        });
    }

    /**
     * Asynchronously perform a {@link SearchQuery} and return the raw N1QL response as a {@link JsonObject}.
     *
     * Note that the query is executed "as is", without any processing comparable to what is done in
     * {@link AsyncBucket#query(SearchQuery)} (like enforcing a server side timeout).
     *
     * @param query the query to execute.
     * @return an {@link Observable} of the FTS response as a {@link JsonObject}.
     */
    public Observable ftsToJsonObject(SearchQuery query) {
        return ftsToRawCustom(query, new Func1() {
            @Override
            public JsonObject call(String stringResponse) {
                return JsonObject.fromJson(stringResponse);
            }
        });
    }

    /**
     * Asynchronously perform a {@link SearchQuery} and return the raw N1QL response as a String.
     *
     * Note that the query is executed "as is", without any processing comparable to what is done in
     * {@link AsyncBucket#query(SearchQuery)} (like enforcing a server side timeout).
     *
     * @param query the query to execute.
     * @return an {@link Observable} of the FTS response as a String.
     */
    public Observable ftsToRawJson(SearchQuery query) {
        return ftsToRawCustom(query, new Func1() {
            @Override
            public String call(String stringResponse) {
                return stringResponse;
            }
        });
    }

    /**
     * Asynchronously perform a {@link SearchQuery} and apply a user function to deserialize the raw JSON
     * FTS response, which is represented as a {@link String}.
     *
     * Note that the query is executed "as is", without any processing comparable to what is done in
     * {@link AsyncBucket#query(SearchQuery)} (like enforcing a server side timeout).
     *
     * @param query the query to execute.
     * @param deserializer a deserializer function that transforms the String representation of the response into a custom type T.
     * @param  the type of the response, once deserialized by the user-provided function.
     * @return an {@link Observable} of the FTS response as a T.
     */
    public  Observable ftsToRawCustom(final SearchQuery query, final Func1 deserializer) {
        final String indexName = query.indexName();
        final AbstractFtsQuery queryPart = query.query();

        return Observable.defer(new Func0>() {
            @Override
            public Observable call() {
                SearchQueryRequest request = new SearchQueryRequest(indexName, query.export().toString(), bucket, password);
                return core.send(request);
            }
        }).map(new Func1() {
            @Override
            public T call(SearchQueryResponse response) {
                if (response.status().isSuccess()) {
                    return deserializer.call(response.payload());
                } else if (response.status() == ResponseStatus.INVALID_ARGUMENTS) {
                    throw new FtsMalformedRequestException(response.payload());
                } else if (response.status() == ResponseStatus.FAILURE) {
                    throw new FtsConsistencyTimeoutException();
                } else {
                    throw new CouchbaseException("Could not query search index, " + response.status() + ": " + response.payload());
                }
            }
        });
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy