main.java.com.cloudant.client.api.Search Maven / Gradle / Ivy
Show all versions of cloudant-client Show documentation
/*
* Copyright © 2015, 2019 IBM Corp. All rights reserved.
*
* 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.cloudant.client.api;
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.jsonToObject;
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.assertNotEmpty;
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.close;
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.getAsLong;
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.getAsString;
import com.cloudant.client.api.model.SearchResult;
import com.cloudant.client.internal.DatabaseURIHelper;
import com.cloudant.http.Http;
import com.cloudant.http.HttpConnection;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Logger;
/**
* This class provides access to the Cloudant Search APIs.
* Note: The design document name and search index name are required.
* Do not include the {@code _design} prefix from the {@code _id} of the design document.
*
Usage Example:
*
* {@code
* // Search query using design document _id '_design/views101' and search index 'animals'
* List birds = db.search("views101/animals")
* .limit(10)
* .includeDocs(true)
* .query("class:bird", Bird.class);
*
* // groups
* Map> birdGroups = db.search("views101/animals")
* .limit(10)
* .includeDocs(true)
* .queryGroups("class:bird", Bird.class);
* for ( Entry> group : birdGroups.entrySet()) {
* System.out.println("Group Name : " + group.getKey());
* for ( Bird b : group.getValue() ) {
* System.out.println("\t" + b);
* }
* }
*
* // search results object
* SearchResult result = db.search("views101/animals")
* .querySearchResult("class:bird", Bird.class);
*
* // pagination
* SearchResult nextPage = db.search("views101/animals")
* .bookmark(result.bookmark)
* .querySearchResult("class:bird", Bird.class);
* }
*
*
* @author Mario Briggs
* @see Database#search(String)
* @see SearchResult
* @since 0.0.1
*/
public class Search {
private static final Logger log = Logger.getLogger(Search.class.getCanonicalName());
// search fields
private Integer limit;
private boolean includeDocs = false;
private String bookmark;
private CloudantClient client;
private DatabaseURIHelper databaseHelper;
private final String partitionKey;
Search(CloudantClient client, Database db, String partitionKey, String searchIndexId) {
assertNotEmpty(searchIndexId, "searchIndexId");
this.client = client;
this.partitionKey = partitionKey;
String search = searchIndexId;
this.databaseHelper = new DatabaseURIHelper(db.getDBUri()).partition(partitionKey);
if (searchIndexId.contains("/")) {
String[] v = searchIndexId.split("/");
this.databaseHelper.path("_design").path(v[0]).path("_search").path(v[1]);
} else {
this.databaseHelper.path(search);
}
}
// Query options
/**
* Performs a Cloudant Search and returns the result as an {@link InputStream}
*
* @param query the Lucene query to be passed to the Search index
* The stream should be properly closed after usage, as to avoid connection
* leaks.
* @return The result as an {@link InputStream}.
*/
public InputStream queryForStream(String query) {
key(query);
URI uri = databaseHelper.build();
HttpConnection get = Http.GET(uri);
get.requestProperties.put("Accept", "application/json");
return client.couchDbClient.executeToInputStream(get);
}
/**
* Queries a Search Index and returns ungrouped results. In case the query
* used grouping, an empty list is returned
*
* @param Object type T
* @param query the Lucene query to be passed to the Search index
* @param classOfT The class of type T
* @return The result of the search query as a {@code List }
*/
public List query(String query, Class classOfT) {
InputStream instream = null;
List result = new ArrayList();
try {
Reader reader = new InputStreamReader(instream = queryForStream(query), "UTF-8");
JsonObject json = new JsonParser().parse(reader).getAsJsonObject();
if (json.has("rows")) {
if (!includeDocs) {
log.warning("includeDocs set to false and attempting to retrieve doc. " +
"null object will be returned");
}
for (JsonElement e : json.getAsJsonArray("rows")) {
result.add(jsonToObject(client.getGson(), e, "doc", classOfT));
}
} else {
log.warning("No ungrouped result available. Use queryGroups() if grouping set");
}
return result;
} catch (UnsupportedEncodingException e1) {
// This should never happen as every implementation of the java platform is required
// to support UTF-8.
throw new RuntimeException(e1);
} finally {
close(instream);
}
}
/**
* Queries a Search Index and returns grouped results in a map where key
* of the map is the groupName. In case the query didnt use grouping,
* an empty map is returned
*
* @param Object type T
* @param query the Lucene query to be passed to the Search index
* @param classOfT The class of type T
* @return The result of the grouped search query as a ordered {@code Map }
*/
public Map> queryGroups(String query, Class classOfT) {
InputStream instream = null;
try {
Reader reader = new InputStreamReader(instream = queryForStream(query), "UTF-8");
JsonObject json = new JsonParser().parse(reader).getAsJsonObject();
Map> result = new LinkedHashMap>();
if (json.has("groups")) {
for (JsonElement e : json.getAsJsonArray("groups")) {
String groupName = e.getAsJsonObject().get("by").getAsString();
List orows = new ArrayList();
if (!includeDocs) {
log.warning("includeDocs set to false and attempting to retrieve doc. " +
"null object will be returned");
}
for (JsonElement rows : e.getAsJsonObject().getAsJsonArray("rows")) {
orows.add(jsonToObject(client.getGson(), rows, "doc", classOfT));
}
result.put(groupName, orows);
}// end for(groups)
}// end hasgroups
else {
log.warning("No grouped results available. Use query() if non grouped query");
}
return result;
} catch (UnsupportedEncodingException e1) {
// This should never happen as every implementation of the java platform is required
// to support UTF-8.
throw new RuntimeException(e1);
} finally {
close(instream);
}
}
/**
* Performs a Cloudant Search and returns the result as an {@link SearchResult}
*
* @param Object type T, an instance into which the rows[].doc/group[].rows[].doc
* attribute of the Search result response should be deserialized into. Same
* goes for the rows[].fields/group[].rows[].fields attribute
* @param query the Lucene query to be passed to the Search index
* @param classOfT The class of type T.
* @return The Search result entries
*/
public SearchResult querySearchResult(String query, Class classOfT) {
InputStream instream = null;
try {
Reader reader = new InputStreamReader(instream = queryForStream(query), "UTF-8");
JsonObject json = new JsonParser().parse(reader).getAsJsonObject();
SearchResult sr = new SearchResult();
sr.setTotalRows(getAsLong(json, "total_rows"));
sr.setBookmark(getAsString(json, "bookmark"));
if (json.has("rows")) {
sr.setRows(getRows(json.getAsJsonArray("rows"), sr, classOfT));
} else if (json.has("groups")) {
setGroups(json.getAsJsonArray("groups"), sr, classOfT);
}
if (json.has("counts")) {
sr.setCounts(getFieldsCounts(json.getAsJsonObject("counts").entrySet()));
}
if (json.has("ranges")) {
sr.setRanges(getFieldsCounts(json.getAsJsonObject("ranges").entrySet()));
}
return sr;
} catch (UnsupportedEncodingException e) {
// This should never happen as every implementation of the java platform is required
// to support UTF-8.
throw new RuntimeException(e);
} finally {
close(instream);
}
}
/**
* @param limit limit the number of documents in the result
* @return this for additional parameter setting or to query
*/
public Search limit(Integer limit) {
this.limit = limit;
databaseHelper.query("limit", this.limit);
return this;
}
/**
* Control which page of results to get. The bookmark value is obtained by executing
* the query()/queryForStream() once and getting it from the bookmark field
* in the response
*
* @param bookmark see the next page of results after this bookmark result
* @return this for additional parameter setting or to query
*/
public Search bookmark(String bookmark) {
this.bookmark = bookmark;
databaseHelper.query("bookmark", this.bookmark);
return this;
}
/**
* Specify the sort order for the result.
*
* @param sortJson JSON string specifying the sort order
* @return this for additional parameter setting or to query
* @see
* Search query syntax
*/
public Search sort(String sortJson) {
assertNotEmpty(sortJson, "sort");
databaseHelper.query("sort", sortJson);
return this;
}
/**
* Group results by the specified field.
*
* @param fieldName by which to group results
* @param isNumber whether field isNumeric.
* @return this for additional parameter setting or to query
*/
public Search groupField(String fieldName, boolean isNumber) {
assertNotEmpty(fieldName, "fieldName");
if (isNumber) {
databaseHelper.query("group_field", fieldName + "");
} else {
databaseHelper.query("group_field", fieldName);
}
return this;
}
/**
* Maximum group count when groupField is set
*
* @param limit the maximum group count
* @return this for additional parameter setting or to query
*/
public Search groupLimit(int limit) {
databaseHelper.query("group_limit", limit);
return this;
}
/**
* the sort order of the groups when groupField is set
*
* @param groupsortJson JSON string specifying the group sort
* @return this for additional parameter setting or to query
* @see
* Search query syntax
*/
public Search groupSort(String groupsortJson) {
assertNotEmpty(groupsortJson, "groupsortJson");
databaseHelper.query("group_sort", groupsortJson);
return this;
}
/**
* Ranges for faceted searches
*
* @param rangesJson JSON string specifying the ranges
* @return this for additional parameter setting or to query
* @see
* Search query syntax
*/
public Search ranges(String rangesJson) {
assertNotEmpty(rangesJson, "rangesJson");
databaseHelper.query("ranges", rangesJson);
return this;
}
/**
* Array of fieldNames for which counts should be produced
*
* @param countsfields array of the field names
* @return this for additional parameter setting or to query
*/
public Search counts(String[] countsfields) {
assert (countsfields.length > 0);
JsonArray countsJsonArray = new JsonArray();
for(String countsfield : countsfields) {
JsonPrimitive element = new JsonPrimitive(countsfield);
countsJsonArray.add(element);
}
databaseHelper.query("counts", countsJsonArray);
return this;
}
/**
* @param fieldName the name of the field
* @param fieldValue the value of the field
* @return this for additional parameter setting or to query
* @see
* drilldown query parameter
* @deprecated Use {@link #drillDown(String, String...)}
*/
@Deprecated
public Search drillDown(String fieldName, String fieldValue) {
assertNotEmpty(fieldName, "fieldName");
assertNotEmpty(fieldValue, "fieldValue");
JsonArray drillDownArray = new JsonArray();
JsonPrimitive fieldNamePrimitive = new JsonPrimitive(fieldName);
drillDownArray.add(fieldNamePrimitive);
JsonPrimitive fieldValuePrimitive = new JsonPrimitive(fieldValue);
drillDownArray.add(fieldValuePrimitive);
databaseHelper.query("drilldown", drillDownArray, false);
return this;
}
/**
* @param fieldName the name of the field
* @param fieldValues field values to add
* @return this for additional parameter setting or to query
* @see drilldown query parameter
*/
public Search drillDown(String fieldName, String... fieldValues) {
assertNotEmpty(fieldName, "fieldName");
assertNotEmpty(fieldValues, "fieldValues");
JsonArray drillDownArray = new JsonArray();
JsonPrimitive fieldNamePrimitive = new JsonPrimitive(fieldName);
drillDownArray.add(fieldNamePrimitive);
for (String fieldValue : fieldValues) {
JsonPrimitive fieldValuePrimitive = new JsonPrimitive(fieldValue);
drillDownArray.add(fieldValuePrimitive);
}
databaseHelper.query("drilldown", drillDownArray, false);
return this;
}
/**
* @param stale Accept values: ok
* @return this for additional parameter setting or to query
*/
public Search stale(boolean stale) {
if (stale) {
databaseHelper.query("stale", "ok");
}
return this;
}
/**
* @param includeDocs whether to include the document in the result
* @return this for additional parameter setting or to query
*/
public Search includeDocs(Boolean includeDocs) {
this.includeDocs = includeDocs;
databaseHelper.query("include_docs", this.includeDocs);
return this;
}
private void key(String query) {
databaseHelper.query("q", query);
}
private Map> getFieldsCounts(Set>
fldset) {
Map> map = new HashMap>();
for (Entry fld : fldset) {
String field = fld.getKey();
Map ovalues = new HashMap();
if (fld.getValue().isJsonObject()) {
Set> values = fld.getValue().getAsJsonObject()
.entrySet();
for (Entry value : values) {
ovalues.put(value.getKey(), value.getValue().getAsLong());
}
}
map.put(field, ovalues);
}
return map;
}
private List.SearchResultRow> getRows(
JsonArray jsonrows, SearchResult sr, Class classOfT) {
List.SearchResultRow> ret = new ArrayList
.SearchResultRow>();
for (JsonElement e : jsonrows) {
SearchResult.SearchResultRow row = sr.new SearchResultRow();
JsonObject oe = e.getAsJsonObject();
row.setId(oe.get("id").getAsString());
row.setOrder(jsonToObject(client.getGson(), e, "order", Object[].class));
row.setFields(jsonToObject(client.getGson(), e, "fields", classOfT));
if (includeDocs) {
row.setDoc(jsonToObject(client.getGson(), e, "doc", classOfT));
}
ret.add(row);
}
return ret;
}
private void setGroups(JsonArray jsongroups, SearchResult sr, Class classOfT) {
for (JsonElement e : jsongroups) {
SearchResult.SearchResultGroup group = sr.new SearchResultGroup();
JsonObject oe = e.getAsJsonObject();
group.setBy(oe.get("by").getAsString());
group.setTotalRows(oe.get("total_rows").getAsLong());
group.setRows(getRows(oe.getAsJsonArray("rows"), sr, classOfT));
sr.getGroups().add(group);
}
}
}