Skip to content
This repository was archived by the owner on Jan 31, 2022. It is now read-only.

Commit 2904401

Browse files
author
Xavier Grand
committed
Add disjunctiveFaceting helper
1 parent 6dcb6a7 commit 2904401

File tree

6 files changed

+261
-0
lines changed

6 files changed

+261
-0
lines changed

algoliasearch/src/androidTest/java/com/algolia/search/saas/ApplicationTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,17 @@
2929

3030
import com.algolia.search.saas.listeners.GetObjectsListener;
3131
import com.algolia.search.saas.listeners.IndexingListener;
32+
import com.algolia.search.saas.listeners.SearchDisjunctiveFacetingListener;
3233
import com.algolia.search.saas.listeners.SearchListener;
3334
import com.algolia.search.saas.listeners.WaitTaskListener;
3435

3536
import org.json.JSONArray;
3637
import org.json.JSONObject;
3738

3839
import java.util.ArrayList;
40+
import java.util.HashMap;
3941
import java.util.List;
42+
import java.util.Map;
4043
import java.util.concurrent.CountDownLatch;
4144
import java.util.concurrent.TimeUnit;
4245

@@ -107,6 +110,30 @@ public void searchError(Index index, Query query, AlgoliaException e) {
107110
assertTrue(signal.await(Helpers.wait, TimeUnit.SECONDS));
108111
}
109112

113+
@UiThreadTest
114+
public void testSearchDisjunctiveFacetingAsync() throws Exception {
115+
final CountDownLatch signal = new CountDownLatch(1);
116+
117+
class Listener implements SearchDisjunctiveFacetingListener {
118+
@Override
119+
public void searchDisjunctiveFacetingResult(Index index, Query query, List<String> disjunctiveFacets, Map<String, List<String>> refinements, JSONObject results) {
120+
assertEquals(objects.size(), results.optInt("nbHits"));
121+
signal.countDown();
122+
}
123+
124+
@Override
125+
public void searchDisjunctiveFacetingError(Index index, Query query, List<String> disjunctiveFacets, Map<String, List<String>> refinements, AlgoliaException e) {
126+
fail(String.format("Error during search: %s", e.getMessage()));
127+
signal.countDown();
128+
}
129+
}
130+
131+
final Listener searchListener = new Listener();
132+
133+
index.searchDisjunctiveFacetingAsync(new Query(), new ArrayList<String>(), new HashMap<String, List<String>>(), searchListener);
134+
assertTrue(signal.await(Helpers.wait, TimeUnit.SECONDS));
135+
}
136+
110137
@UiThreadTest
111138
public void testAddObjectAsync() throws Exception {
112139
final CountDownLatch signal = new CountDownLatch(1);

algoliasearch/src/main/java/com/algolia/search/saas/BaseIndex.java

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@
3030
import java.io.UnsupportedEncodingException;
3131
import java.net.URLEncoder;
3232
import java.util.ArrayList;
33+
import java.util.HashMap;
34+
import java.util.Iterator;
3335
import java.util.List;
36+
import java.util.Map;
3437

3538
/*
3639
* Abstract class for Index methods
@@ -404,4 +407,141 @@ protected JSONObject setSettings(JSONObject settings) throws AlgoliaException {
404407
protected JSONObject clearIndex() throws AlgoliaException {
405408
return client.postRequest("/1/indexes/" + encodedIndexName + "/clear", "", false);
406409
}
410+
411+
/**
412+
* Perform a search with disjunctive facets generating as many queries as number of disjunctive facets
413+
*
414+
* @param query the query
415+
* @param disjunctiveFacets the array of disjunctive facets
416+
* @param refinements Map<String, List<String>> representing the current refinements
417+
* ex: { "my_facet1" => ["my_value1", "my_value2"], "my_disjunctive_facet1" => ["my_value1", "my_value2"] }
418+
* @throws AlgoliaException
419+
*/
420+
421+
public JSONObject searchDisjunctiveFaceting(Query query, List<String> disjunctiveFacets, Map<String, List<String>> refinements) throws AlgoliaException {
422+
if (refinements == null) {
423+
refinements = new HashMap<String, List<String>>();
424+
}
425+
HashMap<String, List<String>> disjunctiveRefinements = new HashMap<String, List<String>>();
426+
for (Map.Entry<String, List<String>> elt : refinements.entrySet()) {
427+
if (disjunctiveFacets.contains(elt.getKey())) {
428+
disjunctiveRefinements.put(elt.getKey(), elt.getValue());
429+
}
430+
}
431+
432+
// build queries
433+
List<IndexQuery> queries = new ArrayList<IndexQuery>();
434+
// hits + regular facets query
435+
StringBuilder filters = new StringBuilder();
436+
boolean first_global = true;
437+
for (Map.Entry<String, List<String>> elt : refinements.entrySet()) {
438+
StringBuilder or = new StringBuilder();
439+
or.append("(");
440+
boolean first = true;
441+
for (String val : elt.getValue()) {
442+
if (disjunctiveRefinements.containsKey(elt.getKey())) {
443+
// disjunctive refinements are ORed
444+
if (!first) {
445+
or.append(',');
446+
}
447+
first = false;
448+
or.append(String.format("%s:%s", elt.getKey(), val));
449+
} else {
450+
if (!first_global) {
451+
filters.append(',');
452+
}
453+
first_global = false;
454+
filters.append(String.format("%s:%s", elt.getKey(), val));
455+
}
456+
}
457+
// Add or
458+
if (disjunctiveRefinements.containsKey(elt.getKey())) {
459+
or.append(')');
460+
if (!first_global) {
461+
filters.append(',');
462+
}
463+
first_global = false;
464+
filters.append(or.toString());
465+
}
466+
}
467+
468+
queries.add(new IndexQuery(this.indexName, new Query(query).setFacetFilters(filters.toString())));
469+
// one query per disjunctive facet (use all refinements but the current one + hitsPerPage=1 + single facet
470+
for (String disjunctiveFacet : disjunctiveFacets) {
471+
filters = new StringBuilder();
472+
first_global = true;
473+
for (Map.Entry<String, List<String>> elt : refinements.entrySet()) {
474+
if (disjunctiveFacet.equals(elt.getKey())) {
475+
continue;
476+
}
477+
StringBuilder or = new StringBuilder();
478+
or.append("(");
479+
boolean first = true;
480+
for (String val : elt.getValue()) {
481+
if (disjunctiveRefinements.containsKey(elt.getKey())) {
482+
// disjunctive refinements are ORed
483+
if (!first) {
484+
or.append(',');
485+
}
486+
first = false;
487+
or.append(String.format("%s:%s", elt.getKey(), val));
488+
} else {
489+
if (!first_global) {
490+
filters.append(',');
491+
}
492+
first_global = false;
493+
filters.append(String.format("%s:%s", elt.getKey(), val));
494+
}
495+
}
496+
// Add or
497+
if (disjunctiveRefinements.containsKey(elt.getKey())) {
498+
or.append(')');
499+
if (!first_global) {
500+
filters.append(',');
501+
}
502+
first_global = false;
503+
filters.append(or.toString());
504+
}
505+
}
506+
List<String> facets = new ArrayList<String>();
507+
facets.add(disjunctiveFacet);
508+
queries.add(new IndexQuery(this.indexName, new Query(query).setHitsPerPage(0).enableAnalytics(false).setAttributesToRetrieve(new ArrayList<String>()).setAttributesToHighlight(new ArrayList<String>()).setAttributesToSnippet(new ArrayList<String>()).setFacets(facets).setFacetFilters(filters.toString())));
509+
}
510+
JSONObject answers = this.client.multipleQueries(queries, null);
511+
512+
// aggregate answers
513+
// first answer stores the hits + regular facets
514+
try {
515+
JSONArray results = answers.getJSONArray("results");
516+
JSONObject aggregatedAnswer = results.getJSONObject(0);
517+
JSONObject disjunctiveFacetsJSON = new JSONObject();
518+
for (int i = 1; i < results.length(); ++i) {
519+
JSONObject facets = results.getJSONObject(i).getJSONObject("facets");
520+
@SuppressWarnings("unchecked")
521+
Iterator<String> keys = facets.keys();
522+
while (keys.hasNext()) {
523+
String key = keys.next();
524+
// Add the facet to the disjunctive facet hash
525+
disjunctiveFacetsJSON.put(key, facets.getJSONObject(key));
526+
// concatenate missing refinements
527+
if (!disjunctiveRefinements.containsKey(key)) {
528+
continue;
529+
}
530+
for (String refine : disjunctiveRefinements.get(key)) {
531+
if (!disjunctiveFacetsJSON.getJSONObject(key).has(refine)) {
532+
disjunctiveFacetsJSON.getJSONObject(key).put(refine, 0);
533+
}
534+
}
535+
}
536+
}
537+
aggregatedAnswer.put("disjunctiveFacets", disjunctiveFacetsJSON);
538+
return aggregatedAnswer;
539+
} catch (JSONException e) {
540+
throw new Error(e);
541+
}
542+
}
543+
544+
public JSONObject searchDisjunctiveFaceting(Query query, List<String> disjunctiveFacets) throws AlgoliaException {
545+
return searchDisjunctiveFaceting(query, disjunctiveFacets, null);
546+
}
407547
}

algoliasearch/src/main/java/com/algolia/search/saas/Index.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@
2929
import com.algolia.search.saas.listeners.GetObjectsListener;
3030
import com.algolia.search.saas.listeners.IndexingListener;
3131
import com.algolia.search.saas.listeners.SearchListener;
32+
import com.algolia.search.saas.listeners.SearchDisjunctiveFacetingListener;
3233
import com.algolia.search.saas.listeners.SettingsListener;
3334
import com.algolia.search.saas.listeners.WaitTaskListener;
3435

3536
import org.json.JSONArray;
3637
import org.json.JSONObject;
3738

3839
import java.util.List;
40+
import java.util.Map;
3941

4042
/**
4143
* Contains all the functions related to one index
@@ -80,6 +82,41 @@ public void searchASync(Query query, SearchListener listener) {
8082
new ASyncSearchTask().execute(params);
8183
}
8284

85+
////////////////////////////////////////////////////////////////////////////////////////////////
86+
/// SEARCH DISJUNCTIVE FACETING TASK
87+
////////////////////////////////////////////////////////////////////////////////////////////////
88+
private class AsyncSearchDisjunctiveFacetingTask extends AsyncTask<TaskParams.SearchDisjunctiveFaceting, Void, TaskParams.SearchDisjunctiveFaceting> {
89+
@Override
90+
protected TaskParams.SearchDisjunctiveFaceting doInBackground(TaskParams.SearchDisjunctiveFaceting... params) {
91+
TaskParams.SearchDisjunctiveFaceting p = params[0];
92+
try {
93+
p.content = searchDisjunctiveFaceting(p.query, p.disjunctiveFacets, p.refinements);
94+
} catch (AlgoliaException e) {
95+
p.error = e;
96+
}
97+
return p;
98+
}
99+
100+
@Override
101+
protected void onPostExecute(TaskParams.SearchDisjunctiveFaceting p) {
102+
p.sendResult(Index.this);
103+
}
104+
}
105+
106+
/**
107+
* Perform a search with disjunctive facets generating as many queries as number of disjunctive facets
108+
*
109+
* @param query the query
110+
* @param disjunctiveFacets the array of disjunctive facets
111+
* @param refinements Map<String, List<String>> representing the current refinements
112+
* ex: { "my_facet1" => ["my_value1", "my_value2"], "my_disjunctive_facet1" => ["my_value1", "my_value2"] }
113+
* @param listener the listener that will receive the result or error.
114+
*/
115+
public void searchDisjunctiveFacetingAsync(Query query, List<String> disjunctiveFacets, Map<String, List<String>> refinements, SearchDisjunctiveFacetingListener listener) {
116+
TaskParams.SearchDisjunctiveFaceting params = new TaskParams.SearchDisjunctiveFaceting(listener, query, disjunctiveFacets, refinements);
117+
new AsyncSearchDisjunctiveFacetingTask().execute(params);
118+
}
119+
83120
////////////////////////////////////////////////////////////////////////////////////////////////
84121
/// INDEXING TASK
85122
////////////////////////////////////////////////////////////////////////////////////////////////

algoliasearch/src/main/java/com/algolia/search/saas/IndexMethod.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
public enum IndexMethod {
2727
Search,
28+
searchDisjunctiveFaceting,
2829

2930
AddObject,
3031
AddObjectWithObjectID,

algoliasearch/src/main/java/com/algolia/search/saas/TaskParams.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@
2828
import com.algolia.search.saas.listeners.GetObjectsListener;
2929
import com.algolia.search.saas.listeners.IndexingListener;
3030
import com.algolia.search.saas.listeners.SearchListener;
31+
import com.algolia.search.saas.listeners.SearchDisjunctiveFacetingListener;
3132
import com.algolia.search.saas.listeners.SettingsListener;
3233
import com.algolia.search.saas.listeners.WaitTaskListener;
3334

3435
import org.json.JSONArray;
3536
import org.json.JSONObject;
3637

3738
import java.util.List;
39+
import java.util.Map;
3840

3941
public class TaskParams {
4042
public static class Search {
@@ -62,6 +64,35 @@ protected void sendResult(Index index) {
6264
}
6365
}
6466

67+
public static class SearchDisjunctiveFaceting {
68+
protected SearchDisjunctiveFacetingListener listener;
69+
public Query query;
70+
public List<String> disjunctiveFacets;
71+
public Map<String, List<String>> refinements;
72+
73+
protected JSONObject content;
74+
protected AlgoliaException error;
75+
76+
protected SearchDisjunctiveFaceting(SearchDisjunctiveFacetingListener listener, Query query, List<String> disjunctiveFacets, Map<String, List<String>> refinements) {
77+
this.listener = listener;
78+
this.query = query;
79+
this.disjunctiveFacets = disjunctiveFacets;
80+
this.refinements = refinements;
81+
}
82+
83+
protected void sendResult(Index index) {
84+
if (listener == null) {
85+
return;
86+
}
87+
88+
if (error == null) {
89+
listener.searchDisjunctiveFacetingResult(index, query, disjunctiveFacets, refinements, content);
90+
} else {
91+
listener.searchDisjunctiveFacetingError(index, query, disjunctiveFacets, refinements, error);
92+
}
93+
}
94+
}
95+
6596
public static class Indexing {
6697
protected IndexingListener listener;
6798
public IndexMethod method;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.algolia.search.saas.listeners;
2+
3+
import com.algolia.search.saas.Index;
4+
import com.algolia.search.saas.Query;
5+
import com.algolia.search.saas.AlgoliaException;
6+
7+
import org.json.JSONObject;
8+
9+
import java.util.List;
10+
import java.util.Map;
11+
12+
/**
13+
* Asynchronously receive result of search method
14+
*/
15+
public interface SearchDisjunctiveFacetingListener {
16+
/**
17+
* Asynchronously receive result of Index.searchASync method.
18+
*/
19+
void searchDisjunctiveFacetingResult(Index index, Query query, List<String> disjunctiveFacets, Map<String, List<String>> refinements, JSONObject results);
20+
21+
/**
22+
* Asynchronously receive error of Index.searchASync method.
23+
*/
24+
void searchDisjunctiveFacetingError(Index index, Query query, List<String> disjunctiveFacets, Map<String, List<String>> refinements, AlgoliaException e);
25+
}

0 commit comments

Comments
 (0)