/**
* This software is licensed to you under the Apache License, Version 2.0 (the
* "Apache License").
*
* LinkedIn's contributions are made under the Apache License. If you contribute
* to the Software, the contributions will be deemed to have been made under the
* Apache License, unless you expressly indicate otherwise. Please do not make any
* contributions that would be inconsistent with the Apache License.
*
* You may obtain a copy of the Apache License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, this software
* distributed under the Apache License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the Apache
* License for the specific language governing permissions and limitations for the
* software governed under the Apache License.
*
* © 2012 LinkedIn Corp. All Rights Reserved.
*/
package com.senseidb.test;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import junit.framework.TestCase;
import org.apache.commons.configuration.DataConfiguration;
import org.apache.commons.configuration.web.ServletRequestConfiguration;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.SortField;
import org.json.JSONException;
import org.json.JSONObject;
import com.browseengine.bobo.api.BrowseFacet;
import com.browseengine.bobo.api.BrowseSelection;
import com.browseengine.bobo.api.FacetAccessible;
import com.browseengine.bobo.api.FacetSpec;
import com.browseengine.bobo.api.MappedFacetAccessible;
import com.browseengine.bobo.facets.DefaultFacetHandlerInitializerParam;
import com.browseengine.bobo.facets.FacetHandlerInitializerParam;
import com.senseidb.search.req.SenseiHit;
import com.senseidb.search.req.SenseiJSONQuery;
import com.senseidb.search.req.SenseiQuery;
import com.senseidb.search.req.SenseiRequest;
import com.senseidb.search.req.SenseiResult;
import com.senseidb.servlet.DefaultSenseiJSONServlet;
import com.senseidb.svc.api.SenseiException;
import com.senseidb.svc.impl.HttpRestSenseiServiceImpl;
import com.senseidb.util.JSONUtil.FastJSONArray;
import com.senseidb.util.JSONUtil.FastJSONObject;
public class TestHttpRestSenseiServiceImpl extends TestCase
{
private static final int EXPECTED_COUNT = 72;
private static final int EXPECTED_OFFSET = 227;
private static final boolean EXPECTED_FETCH_STORED_FIELDS = true;
private static final boolean EXPECTED_SHOW_EXPLANATION = true;
public TestHttpRestSenseiServiceImpl(String name)
{
super(name);
}
public void testSenseiResultParsing()
throws Exception
{
SenseiRequest aRequest = createNonRandomSenseiRequest();
SenseiResult aResult = createMockResultFromRequest(aRequest);
JSONObject resultJSONObj = DefaultSenseiJSONServlet.buildJSONResult(aRequest, aResult);
SenseiResult bResult = HttpRestSenseiServiceImpl.buildSenseiResult(resultJSONObj);
assertEquals(aResult, bResult);
}
private SenseiResult createMockResultFromRequest(SenseiRequest request) {
SenseiResult result = new SenseiResult();
result.setParsedQuery("This is my parsed query value");
result.setTime(Long.MAX_VALUE / 2);
result.setNumHits(Integer.MAX_VALUE / 2);
result.setTid(1);
result.setTotalDocs(512);
result.setHits(createSenseiHits(10));
result.addAll(createFacetAccessibleMap(request));
return result;
}
private SenseiHit[] createSenseiHits(int count) {
List<SenseiHit> hits = new ArrayList<SenseiHit>();
for (int i = 0; i < count; i++) {
SenseiHit sh = new SenseiHit();
sh.setUID(i);
sh.setDocid(100 + i);
sh.setExplanation(createExplanation(i, i));
sh.setFieldValues(i % 2 == 0 ? createFieldValues(i) : null);
sh.setRawFieldValues(i % 2 == 0 ? createRawFieldValues(i) : null);
sh.setStoredFields(createSenseiHitDocument());
hits.add(sh);
}
return hits.toArray(new SenseiHit[hits.size()]);
}
private Document createSenseiHitDocument() {
Document doc = new Document();
for (int i = 0; i < 10; i++) {
doc.add(new org.apache.lucene.document.Field("name" + i, "value" + i, Field.Store.YES, Field.Index.ANALYZED));
}
return doc;
}
private Map<String,String[]> createFieldValues(int uid) {
Map<String,String[]> map = new HashMap<String,String[]>();
for (int i = 0; i < 10; i++) {
map.put(String.format("key %s %s", uid, i), new String[]{"hello" + i, "world" + i});
}
return map;
}
private Map<String,Object[]> createRawFieldValues(int uid) {
Map<String,Object[]> map = new HashMap<String,Object[]>();
for (int i = 0; i < 10; i++) {
map.put(String.format("key %s %s", uid, i), new String[] { "hello" + i, "world" + i} );
}
return map;
}
private Explanation createExplanation(int facetIndex, int descCount) {
Explanation expl = new Explanation();
expl.setDescription(String.format("facetIndex = %s, and this is my description", facetIndex));
expl.setValue(facetIndex);
for (int i = 0; i < descCount; i++) {
expl.addDetail(createExplanation((1000 * facetIndex) + i, 0));
}
return expl;
}
private Map<String, FacetAccessible> createFacetAccessibleMap(SenseiRequest request) {
Map<String, FacetAccessible> facetAccessibleMap = new HashMap<String, FacetAccessible>();
for (int i = 10; i < 20; i++) {
String fieldName = "fieldName_" + i;
List<BrowseFacet> bfList = new ArrayList<BrowseFacet>();
Map<String,FacetSpec> facetSpecs = request.getFacetSpecs();
// copy the requests facets over
for (String facetName : facetSpecs.keySet()) {
BrowseFacet bf = new BrowseFacet();
bf.setFacetValueHitCount(i);
bf.setValue(String.format("fieldName %s, value = %s", fieldName, facetName));
bfList.add(bf);
}
MappedFacetAccessible mfa = new MappedFacetAccessible(bfList.toArray(new BrowseFacet[bfList.size()]));
facetAccessibleMap.put(fieldName, mfa);
}
return facetAccessibleMap;
}
public void testURIBuilding()
throws JSONException, SenseiException, UnsupportedEncodingException, URISyntaxException, MalformedURLException
{
SenseiRequest aRequest = createNonRandomSenseiRequest();
List<NameValuePair> queryParams = HttpRestSenseiServiceImpl.convertRequestToQueryParams(aRequest);
HttpRestSenseiServiceImpl senseiService = createSenseiService();
URI requestURI = senseiService.buildRequestURI(queryParams);
assertTrue(requestURI.toURL().toString().length() > 0); // force resolving the URI to a string
List<NameValuePair> parsedParams = URLEncodedUtils.parse(requestURI, "UTF-8");
MockServletRequest mockServletRequest = MockServletRequest.create(parsedParams);
DataConfiguration params = new DataConfiguration(new ServletRequestConfiguration(mockServletRequest));
SenseiRequest bRequest = DefaultSenseiJSONServlet.convertSenseiRequest(params);
assertEquals(aRequest, bRequest);
}
public void testConvertSenseiRequest()
throws SenseiException, UnsupportedEncodingException, JSONException
{
SenseiRequest testRequest = createNonRandomSenseiRequest();
List<NameValuePair> list = HttpRestSenseiServiceImpl.convertRequestToQueryParams(testRequest);
MockServletRequest mockServletRequest = MockServletRequest.create(list);
DataConfiguration params = new DataConfiguration(new ServletRequestConfiguration(mockServletRequest));
SenseiRequest resultRequest = DefaultSenseiJSONServlet.convertSenseiRequest(params);
assertEquals(testRequest, resultRequest);
}
public void testConvertScalarValues()
throws SenseiException, UnsupportedEncodingException, JSONException
{
SenseiRequest aRequest = new SenseiRequest();
aRequest.setCount(EXPECTED_COUNT);
aRequest.setOffset(EXPECTED_OFFSET);
aRequest.setFetchStoredFields(EXPECTED_FETCH_STORED_FIELDS);
aRequest.setShowExplanation(EXPECTED_SHOW_EXPLANATION);
SenseiRequest bRequest = new SenseiRequest();
List<NameValuePair> list = HttpRestSenseiServiceImpl.convertRequestToQueryParams(aRequest);
MockServletRequest mockServletRequest = MockServletRequest.create(list);
DataConfiguration params = new DataConfiguration(new ServletRequestConfiguration(mockServletRequest));
DefaultSenseiJSONServlet.convertScalarParams(bRequest, params);
assertEquals(aRequest, bRequest);
}
public void testInitParams()
throws UnsupportedEncodingException
{
SenseiRequest aRequest = new SenseiRequest();
Map<String, FacetHandlerInitializerParam> initParams = createInitParams();
aRequest.putAllFacetHandlerInitializerParams(initParams);
SenseiRequest bRequest = new SenseiRequest();
List<NameValuePair> qparams = new ArrayList<NameValuePair>();
HttpRestSenseiServiceImpl.convertFacetInitParams(qparams, initParams);
MockServletRequest mockServletRequest = MockServletRequest.create(qparams);
DataConfiguration params = new DataConfiguration(new ServletRequestConfiguration(mockServletRequest));
DefaultSenseiJSONServlet.convertInitParams(bRequest, params);
assertEquals(aRequest, bRequest);
}
public void testFacetSpecs()
{
SenseiRequest aRequest = new SenseiRequest();
Map<String, FacetSpec> facetSpecMap = createFacetSpecMap();
aRequest.setFacetSpecs(facetSpecMap);
SenseiRequest bRequest = new SenseiRequest();
List<NameValuePair> list = new ArrayList<NameValuePair>();
HttpRestSenseiServiceImpl.convertFacetSpecs(list, facetSpecMap);
MockServletRequest mockServletRequest = MockServletRequest.create(list);
DataConfiguration params = new DataConfiguration(new ServletRequestConfiguration(mockServletRequest));
DefaultSenseiJSONServlet.convertFacetParam(bRequest, params);
assertEquals(aRequest, bRequest);
}
public void testSortFields()
{
SenseiRequest aRequest = new SenseiRequest();
final SortField[] sortFields = createSortFields();
aRequest.addSortFields(sortFields);
SenseiRequest bRequest = new SenseiRequest();
List<NameValuePair> list = new ArrayList<NameValuePair>();
HttpRestSenseiServiceImpl.convertSortFieldParams(list, sortFields);
MockServletRequest mockServletRequest = MockServletRequest.create(list);
DataConfiguration params = new DataConfiguration(new ServletRequestConfiguration(mockServletRequest));
DefaultSenseiJSONServlet.convertSortParam(bRequest, params);
assertEquals(aRequest, bRequest);
}
public void testSenseiQuery()
throws SenseiException, JSONException
{
SenseiRequest aRequest = new SenseiRequest();
SenseiQuery senseiQuery = createSenseiQuery();
aRequest.setQuery(senseiQuery);
SenseiRequest bRequest = new SenseiRequest();
List<NameValuePair> list = new ArrayList<NameValuePair>();
HttpRestSenseiServiceImpl.convertSenseiQuery(list, senseiQuery);
MockServletRequest mockServletRequest = MockServletRequest.create(list);
DataConfiguration params = new DataConfiguration(new ServletRequestConfiguration(mockServletRequest));
DefaultSenseiJSONServlet.convertSenseiQuery(bRequest, params);
assertEquals(aRequest, bRequest);
}
public void testNullSenseiQuery()
throws SenseiException
{
List<NameValuePair> list = new ArrayList<NameValuePair>();
HttpRestSenseiServiceImpl.convertSenseiQuery(list, null);
assertTrue(list.size() == 0);
}
public void testPartitions()
throws SenseiException, JSONException
{
SenseiRequest aRequest = new SenseiRequest();
Set<Integer> partitions = createPartitions();
aRequest.setPartitions(partitions);
SenseiRequest bRequest = new SenseiRequest();
List<NameValuePair> list = new ArrayList<NameValuePair>();
HttpRestSenseiServiceImpl.convertPartitionParams(list, partitions);
MockServletRequest mockServletRequest = MockServletRequest.create(list);
DataConfiguration params = new DataConfiguration(new ServletRequestConfiguration(mockServletRequest));
DefaultSenseiJSONServlet.convertPartitionParams(bRequest, params);
assertEquals(aRequest, bRequest);
}
public void testNullPartitions() {
List<NameValuePair> list = new ArrayList<NameValuePair>();
HttpRestSenseiServiceImpl.convertPartitionParams(list, null);
assertTrue(list.size() == 0);
}
public void testBrowseSelections()
throws JSONException
{
SenseiRequest aRequest = new SenseiRequest();
BrowseSelection[] selections = createBrowseSelections();
aRequest.addSelections(selections);
SenseiRequest bRequest = new SenseiRequest();
bRequest.addSelections(selections);
List<NameValuePair> list = new ArrayList<NameValuePair>();
HttpRestSenseiServiceImpl.convertSelectionNames(list, bRequest);
MockServletRequest mockServletRequest = MockServletRequest.create(list);
DataConfiguration params = new DataConfiguration(new ServletRequestConfiguration(mockServletRequest));
DefaultSenseiJSONServlet.convertSelectParam(bRequest, params);
assertEquals(aRequest, bRequest);
}
private HttpRestSenseiServiceImpl createSenseiService() {
return new HttpRestSenseiServiceImpl(
"http",
"localhost",
80,
"/sensei",
2000,
5,
null);
}
private SenseiRequest createNonRandomSenseiRequest()
throws JSONException
{
SenseiRequest req = new SenseiRequest();
createScalarValues(req);
req.setFacetHandlerInitParamMap(createInitParams());
req.setFacetSpecs(createFacetSpecMap());
req.setSort(createSortFields());
req.setQuery(createSenseiQuery());
req.addSelections(createBrowseSelections());
req.setPartitions(createPartitions());
return req;
}
void createScalarValues(SenseiRequest req) {
req.setCount(EXPECTED_COUNT);
req.setOffset(EXPECTED_OFFSET);
req.setFetchStoredFields(EXPECTED_FETCH_STORED_FIELDS);
req.setShowExplanation(EXPECTED_SHOW_EXPLANATION);
}
Set<Integer> createPartitions() {
HashSet<Integer> partitions = new HashSet<Integer>();
for (int i = 0; i < 10; i++) {
partitions.add(i*i);
}
return partitions;
}
BrowseSelection[] createBrowseSelections() {
List<BrowseSelection> list = new ArrayList<BrowseSelection>();
BrowseSelection selection;
selection = new BrowseSelection("aSelection");
selection.addNotValue("notVal");
selection.addValue("aVal");
selection.setSelectionOperation(BrowseSelection.ValueOperation.ValueOperationAnd);
list.add(selection);
return list.toArray(new BrowseSelection[list.size()]);
}
SenseiQuery createSenseiQuery()
throws JSONException
{
JSONObject obj = new FastJSONObject();
obj.put("query", "key words are useful"); // 'query' in the JSONObj gets translated into 'q' in the GET request
for (int i = 0; i < 10; i++) {
obj.put("key" + i, "val" + i);
}
SenseiQuery query = new SenseiJSONQuery(obj);
return query;
}
SortField[] createSortFields() {
List<SortField> list = new ArrayList<SortField>();
list.add(new SortField(null, SortField.DOC));
list.add(new SortField(null, SortField.DOC, true));
list.add(new SortField(null, SortField.SCORE));
list.add(new SortField(null, SortField.SCORE, true));
list.add(new SortField("fieldCUSTOM", SortField.CUSTOM, false));
list.add(new SortField("fieldCUSTOMREV", SortField.CUSTOM, true));
return list.toArray(new SortField[list.size()]);
}
Map<String, FacetSpec> createFacetSpecMap()
{
Map<String, FacetSpec> map = new HashMap<String, FacetSpec>();
FacetSpec spec = new FacetSpec();
spec.setExpandSelection(false);
spec.setMaxCount(10);
spec.setMinHitCount(2);
spec.setOrderBy(FacetSpec.FacetSortSpec.OrderHitsDesc);
map.put("facet1", spec);
spec = new FacetSpec();
spec.setExpandSelection(true);
spec.setMaxCount(5);
spec.setMinHitCount(10);
spec.setOrderBy(FacetSpec.FacetSortSpec.OrderValueAsc);
map.put("facet2", spec);
for (int i = 3; i < 10; i++) {
spec = new FacetSpec();
spec.setExpandSelection(i % 2 == 0);
spec.setMaxCount(i * 5);
spec.setMinHitCount(i);
spec.setOrderBy(i % 2 == 0 ? FacetSpec.FacetSortSpec.OrderValueAsc : FacetSpec.FacetSortSpec.OrderHitsDesc);
map.put("facet" + i, spec);
}
/* NOT YET SUPPORTED
spec = new FacetSpec();
spec.setExpandSelection(true);
spec.setMaxCount(50);
spec.setMinHitCount(9);
spec.setOrderBy(FacetSpec.FacetSortSpec.OrderByCustom);
map.put("facet3", spec);
*/
return map;
}
Map<String, FacetHandlerInitializerParam> createInitParams()
{
Map<String, FacetHandlerInitializerParam> map = new HashMap<String, FacetHandlerInitializerParam>();
DefaultFacetHandlerInitializerParam param;
param = new DefaultFacetHandlerInitializerParam();
for (int i = 0; i < 2; i++) {
param.putBooleanParam("boolParam" + i, new boolean[]{false});
map.put("boolFacet" + i, param);
}
param = new DefaultFacetHandlerInitializerParam();
for (int i = 0; i < 2; i++) {
param.putIntParam("intParam" + i, new int[]{42});
map.put("intFacet" + i, param);
}
/* NOT YET SUPPORTED
param = new DefaultFacetHandlerInitializerParam();
param.putByteArrayParam("bytearrayParam", new byte[]{1, 2, 3});
map.put("bytearrayFacet", param);
*/
param = new DefaultFacetHandlerInitializerParam();
for (int i = 0; i < 2; i++) {
param.putStringParam("stringParam" + i, new ArrayList<String>(){{ add("woot"); }});
map.put("stringFacet" + i, param);
}
param = new DefaultFacetHandlerInitializerParam();
for (int i = 0; i < 2; i++) {
param.putDoubleParam("doubleParam" + i, new double[]{3.141592});
map.put("doubleFacet" + i, param);
}
param = new DefaultFacetHandlerInitializerParam();
for (int i = 0; i < 2; i++) {
param.putLongParam("longParam"+i, new long[]{3141592, 123456});
map.put("longFacet"+i, param);
}
return map;
}
}