io.orchestrate.client.SearchOperation.java Source code

Java tutorial

Introduction

Here is the source code for io.orchestrate.client.SearchOperation.java

Source

/*
 * Copyright 2013 the original author or authors.
 *
 * 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 io.orchestrate.client;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.glassfish.grizzly.http.HttpHeader;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import static io.orchestrate.client.Preconditions.*;

/**
 * Search for data from the Orchestrate.io service.
 *
 * <p>Usage:
 * <pre>
 * {@code
 * SearchOperation<MyObject> searchOp = SearchOperation
 *         .builder("myCollection", MyObject.class)
 *         .query("*")  // a lucene-style query
 *         .limit(10)   // default
 *         .offset(0)   // default
 *         .build();
 * }
 * </pre>
 *
 * @see <a href="http://java.orchestrate.io/querying/#search">http://java.orchestrate.io/querying/#search</a>
 */
@ToString(callSuper = false)
@EqualsAndHashCode(callSuper = false)
public final class SearchOperation<T> extends AbstractOperation<SearchResults<T>> {

    /** The builder for this search operation. */
    private final Builder<T> builder;

    /**
     * Create a new {@code SearchOperation} on the specified {@code collection}.
     *
     * <p>Equivalent to:
     * <pre>
     * {@code
     * SearchOperation searchOp = SearchOperation.builder("myCollection").build();
     * }
     * </pre>
     *
     * @param collection The collection to search.
     * @param clazz Type information for deserializing to type {@code T} at
     *              runtime.
     * @see Builder#LUCENE_STAR_QUERY
     */
    public SearchOperation(final String collection, final Class<T> clazz) {
        this(builder(collection, clazz));
    }

    /**
     * Create a new {@code SearchOperation} on the specified {@code collection}
     * using the lucene {@code query}.
     *
     * @param collection The collection to search.
     * @param clazz Type information for deserializing to type {@code T} at
     *              runtime.
     * @param query The lucene query to run.
     */
    public SearchOperation(final String collection, final Class<T> clazz, final String query) {
        this(builder(collection, clazz).query(query));
    }

    /**
     * A SearchOperation configured via the {@code Builder}.
     *
     * @param builder The builder used to configure this search operation.
     */
    private SearchOperation(final Builder<T> builder) {
        assert (builder != null);

        this.builder = builder;
    }

    /** {@inheritDoc} */
    @Override
    @SuppressWarnings("unchecked")
    SearchResults<T> fromResponse(final int status, final HttpHeader httpHeader, final String json,
            final JacksonMapper mapper) throws IOException {
        assert (status == 200);

        final ObjectMapper objectMapper = mapper.getMapper();
        final JsonNode jsonNode = objectMapper.readTree(json);

        final int totalCount = jsonNode.get("total_count").asInt();
        final int count = jsonNode.get("count").asInt();
        final List<Result<T>> results = new ArrayList<Result<T>>(count);

        final Iterator<JsonNode> iter = jsonNode.get("results").elements();
        while (iter.hasNext()) {
            final JsonNode result = iter.next();

            // parse result structure (e.g.):
            // {"path":{...},"value":{},"score":1.0}
            final double score = result.get("score").asDouble();
            final KvObject<T> kvObject = jsonToKvObject(objectMapper, result, builder.clazz);

            results.add(new Result<T>(kvObject, score));
        }

        return new SearchResults<T>(results, totalCount);
    }

    /**
     * Returns the collection from this operation.
     *
     * @return The collection from this operation.
     */
    public String getCollection() {
        return builder.collection;
    }

    /**
     * Returns the Lucene query from this operation.
     *
     * @return The Lucene query from this operation.
     */
    public String getQuery() {
        return builder.query;
    }

    /**
     * Returns the limit from this operation.
     *
     * @return The limit from this operation.
     */
    public int getLimit() {
        return builder.limit;
    }

    /**
     * Returns the offset from this operation.
     *
     * @return The offset from this operation.
     */
    public int getOffset() {
        return builder.offset;
    }

    /**
     * A new builder to create a {@code SearchOperation} with default settings.
     *
     * @param collection The name of the collection to search.
     * @return A new {@code Builder} with default settings.
     */
    public static <T> Builder<T> builder(final String collection, final Class<T> clazz) {
        checkNotNullOrEmpty(collection, "collection");

        return new Builder<T>(collection, clazz);
    }

    /**
     * A new builder to create a {@code SearchOperation} based on the specified
     * {@code searchOp}.
     *
     * @param searchOp The search operation to copy settings from.
     * @return A new {@code Builder} from the specified operation.
     */
    public static <T> Builder<T> builder(final SearchOperation<T> searchOp) {
        checkNotNull(searchOp, "searchOp");

        return new Builder<T>(searchOp);
    }

    /**
     * Builder used to create {@code SearchOperation}s.
     */
    @ToString(callSuper = false)
    @EqualsAndHashCode(callSuper = false)
    public static final class Builder<T> {

        /** A blank lucene query. */
        public static final String LUCENE_STAR_QUERY = "*";

        /** The name of the collection to search. */
        private final String collection;
        /** Type information for marshalling objects at runtime. */
        private final Class<T> clazz;
        /** The lucene search query. */
        private String query;
        /** The number of search results to retrieve. */
        private int limit;
        /** The offset to start search results at. */
        private int offset;

        private Builder(final String collection, final Class<T> clazz) {
            assert (collection != null);

            this.collection = collection;
            this.clazz = clazz;
            this.query = LUCENE_STAR_QUERY;
            this.limit = 10;
            this.offset = 0;
        }

        private Builder(final SearchOperation<T> searchOp) {
            assert (searchOp != null);

            this.collection = searchOp.builder.collection;
            this.clazz = searchOp.builder.clazz;
            this.query = searchOp.builder.query;
            this.limit = searchOp.builder.limit;
            this.offset = searchOp.builder.offset;
        }

        /**
         * The lucene query to make with this search request.
         *
         * @param luceneQuery The lucene query.
         * @return This builder.
         */
        public Builder<T> query(final String luceneQuery) {
            checkNotNullOrEmpty(luceneQuery, "luceneQuery");

            this.query = luceneQuery;
            return this;
        }

        /**
         * The number of search results to get in this query, this value cannot
         * exceed 100.
         *
         * @param limit The number of search results in this query.
         * @return This builder.
         */
        public Builder<T> limit(final int limit) {
            checkArgument(limit >= 0, "'limit' cannot be negative.");
            if (limit > 100) {
                final String message = "'limit' cannot be greater than 100. "
                        + "Currently the Orchestrate.io service limits this value.";
                throw new IllegalArgumentException(message);
            }
            this.limit = limit;
            return this;
        }

        /**
         * The position in the results list to start retrieving results from,
         * this is useful for paginating results.
         *
         * @param offset The position to start retrieving results from.
         * @return This builder.
         */
        public Builder<T> offset(final int offset) {
            checkArgument(offset >= 0, "'offset' cannot be negative.");

            this.offset = offset;
            return this;
        }

        /**
         * Creates a new {@code SearchOperation}.
         *
         * @return A new {@link SearchOperation}.
         */
        public SearchOperation<T> build() {
            return new SearchOperation<T>(this);
        }

    }

}