/*
* © Copyright IBM Corp. 2014
*
* 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.ibm.sbt.services.client.connections.search;
import static com.ibm.sbt.services.client.base.CommonConstants.COMMA;
import static com.ibm.sbt.services.client.base.CommonConstants.INIT_URL_PARAM;
import static com.ibm.sbt.services.client.base.CommonConstants.URL_PARAM;
import static com.ibm.sbt.services.client.base.CommonConstants.UTF8;
import static com.ibm.sbt.services.client.base.ConnectionsConstants.nameSpaceCtx;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Node;
import com.ibm.commons.util.StringUtil;
import com.ibm.commons.xml.xpath.XPathExpression;
import com.ibm.sbt.services.client.ClientServicesException;
import com.ibm.sbt.services.client.ClientService.Args;
import com.ibm.sbt.services.client.base.AtomFeedHandler;
import com.ibm.sbt.services.client.base.BaseService;
import com.ibm.sbt.services.client.base.ConnectionsService;
import com.ibm.sbt.services.client.base.IFeedHandler;
import com.ibm.sbt.services.client.base.datahandlers.EntityList;
import com.ibm.sbt.services.endpoints.Endpoint;
/**
* Use the Search API to perform searches across the installed Connections applications.
*
* Returns a list of results with the specified text in the title, description, or content. Encode the strings. By default, spaces are treated as an AND operator. The following operators are supported:
*
* AND or &&: Searches for items that contain both words. For example: query=red%20AND%20test returns items that contain both the word red and the word test. AND is the default operator.
* NOT or !: Excludes the word that follows the operator from the search. For example: query=test%20NOT%20red returns items that contain the word test, but not the word red.
* OR: Searches for items that contain either of the words. For example: query=test%20OR%20red
* To search for a phrase, enclose the phrase in quotation marks (" ").
* +: The plus sign indicates that the word must be present in the result. For example: query=+test%20red returns only items that contain the word test and many that also contain red, but none that contain only the word red.
* ?: Use a question mark to match individual characters. For example: query=te%3Ft returns items that contain the words test, text, tent, and others that begin with te.
* -: The dash prohibits the return of a given word. This operator is similar to NOT. For example: query=test%20-red returns items that contains the word test, but not the word red.
*
* Note: Wildcard searches are permitted, but wildcard only searches (*) are not.
* For more details about supported operators, see Advanced search options in the Using section of the product documentation.
*
* @author Manish Kataria
* @author Carlos Manias
*/
public class SearchService extends ConnectionsService {
private static final long serialVersionUID = -8445895408209299706L;
/**
* Default Constructor
*/
public SearchService() {
this(DEFAULT_ENDPOINT_NAME);
}
/**
* Constructor
*
* @param endpoint
* Creates SearchService Object with the specified endpoint
*/
public SearchService(String endpoint) {
super(endpoint, DEFAULT_CACHE_SIZE);
}
/**
* Constructor
*
* @param endpoint
* Creates SearchService Object with the specified endpoint
*/
public SearchService(Endpoint endpoint) {
super(endpoint, DEFAULT_CACHE_SIZE);
}
@Override
protected void initServiceMappingKeys(){
serviceMappingKeys = new String[]{"search"};
}
/**
* Lists the elements in an Atom entry representing the result returned by a search.
*
* @param query
* Text to search for
* @param parameters
* for additional parameters
* @return EntityList<Result>
* @throws ClientServicesException
*/
public EntityList<Result> getResults(String query,Map<String, String> parameters) throws ClientServicesException {
Map<String, List<String>> parametersList = new HashMap<String, List<String>>();
for(Map.Entry<String, String> entry : parameters.entrySet()){
String key = entry.getKey();
String value = entry.getValue();
List<String> valueList = new ArrayList<String>();
valueList.add(value);
parametersList.put(key, valueList);
}
return getResultsList(query, parametersList);
}
/**
* Lists the elements in an Atom entry representing the result returned by a search.
*
* @param query
* Text to search for
* @param parameters
* for additional parameters
* @return EntityList<Result>
* @throws ClientServicesException
*/
public EntityList<Result> getResultsList(String query,Map<String, List<String>> parameters) throws ClientServicesException {
if(parameters==null){
parameters= new HashMap<String,List<String>>();
}
List<String> queryList = new ArrayList<String>();
queryList.add(query);
parameters.put("query", queryList);
StringBuilder searchQry = new StringBuilder(SearchUrls.SEARCH.format(this));
addUrlParameters(searchQry, parameters);
return getResultEntityList(searchQry.toString(), new HashMap<String,String>());
}
protected void addUrlParameters(StringBuilder b, Map<String,List<String>> parameters) throws ClientServicesException {
if (parameters != null) {
boolean first = !b.toString().contains("?");
for (Map.Entry<String, List<String>> e : parameters.entrySet()) {
String name = e.getKey();
if (StringUtil.isNotEmpty(name)) {
List<String> values = e.getValue();
for(String val : values){
first = addParameter(b, first, name, val);
}
}
}
}
}
protected boolean addParameter(StringBuilder b, boolean first, String name, String value) throws ClientServicesException {
try {
if (value != null) {
b.append(first ? INIT_URL_PARAM : URL_PARAM);
b.append(name);
b.append('=');
b.append(URLEncoder.encode(value, UTF8));
return false;
}
return first;
} catch (UnsupportedEncodingException ex) {
throw new ClientServicesException(ex);
}
}
/**
* The category document identifies the tags that have been assigned to particular
* items, such as blog posts or community entries. Tags are single-word keywords that
* categorize a posting or entry. A tag classifies the information in the posting or
* entry to make it easier to find the content later. The format of the tags document
* is an Atom publishing protocol (APP) categories document.
*
* @param tag {String}
* Single tag to be search for, for multiple tags use other overloaded method
* @return EntityList<Result>
* @throws ClientServicesException
*/
public EntityList<Result> getResultsByTag(String tag) throws ClientServicesException {
List<String> taglist = new ArrayList<String>();
taglist.add(tag);
return getResultsByTag(taglist,null);
}
/**
* The category document identifies the tags that have been assigned to particular
* items, such as blog posts or community entries. Tags are single-word keywords that
* categorize a posting or entry. A tag classifies the information in the posting or
* entry to make it easier to find the content later. The format of the tags document
* is an Atom publishing protocol (APP) categories document.
*
* @param tags List of Tags to searched for
* @return EntityList<Result>
* @throws ClientServicesException
*/
public EntityList<Result> getResultsByTag(List<String> tags) throws ClientServicesException {
return getResultsByTag(tags,null);
}
/**
* The category document identifies the tags that have been assigned to particular
* items, such as blog posts or community entries. Tags are single-word keywords that
* categorize a posting or entry. A tag classifies the information in the posting or
* entry to make it easier to find the content later. The format of the tags document
* is an Atom publishing protocol (APP) categories document.
*
* @param tags List of Tags to searched for
* @return EntityList<Result>
* @throws ClientServicesException
*/
public EntityList<Result> getResultsByTag(List<String> tags,
Map<String, String> parameters) throws ClientServicesException {
// High level wrapper, provides a convenient mechanism for search for
// tags, uses constraints internally
List<String> formattedTags = new ArrayList<String>();
List<Constraint> constraints = new ArrayList<Constraint>();
formattedTags = createTagConstraint(tags);
Constraint constraint = new Constraint();
constraint.setType("category");
constraint.setValues(formattedTags);
constraints.add(constraint);
return getResultsWithConstraint("", constraints,parameters);
}
/**
*
* @param query Text to search for
* @return ResultList
* @throws ClientServicesException
*/
public EntityList<Result> getMyResults(String query) throws ClientServicesException {
return getMyResults(query, null);
}
/**
*
* @param query
* Text to search for
* @param parameters Map
* for additional parameters
* @return EntityList<Result>
* @throws ClientServicesException
*/
public EntityList<Result> getMyResults(String query, Map<String, String> parameters) throws ClientServicesException {
String searchQry = SearchUrls.MYSEARCH.format(this, SearchUrls.getQuery(query));
return getResultEntityList(searchQry, parameters);
}
/**
* get people by search query
* @param query Text to search for
* @throws ClientServicesException
*/
public EntityList<FacetValue> getPeople(String query) throws ClientServicesException {
return getPeople(query, null);
}
/**
*
* @param query
* Text to search for
* @param parameters Map
* for additional parameters
* @return EntityList<FacetValue>
* @throws ClientServicesException
*/
public EntityList<FacetValue> getPeople(String query,Map<String, String> parameters) throws ClientServicesException {
if(parameters==null){
parameters= new HashMap<String,String>();
}
parameters.put("query", query);
parameters.put("pageSize", "0");
parameters.put("facet", "{\"id\": \"Person\"}");
String searchQry = SearchUrls.SEARCH.format(this);
return getFacetValueEntityList(searchQry, parameters);
}
/**
*
* @param query
* Text to search for
* @return EntityList<FacetValue>
* @throws ClientServicesException
*/
public EntityList<FacetValue> getMyPeople(String query) throws ClientServicesException {
return getMyPeople(query, null);
}
/**
*
* @param query
* Text to search for
* @param parameters Map
* for additional parameters
* @return EntityList<FacetValue>
* @throws ClientServicesException
*/
public EntityList<FacetValue> getMyPeople(String query,Map<String, String> parameters) throws ClientServicesException {
if(parameters==null){
parameters= new HashMap<String,String>();
}
parameters.put("query", query);
parameters.put("pageSize", "0");
parameters.put("facet", "{\"id\": \"Person\"}");
String searchQry = SearchUrls.MYSEARCH.format(this);
return getFacetValueEntityList(searchQry, parameters);
}
/**
* @param query Text to search for
* @param constraint Constraint
*
* @return EntityList<Result>
* @throws ClientServicesException
*
* @see <a href="http://www-10.IBM.com/ldd/appdevwiki.nsf/xpDocViewer.xsp?lookupName=IBM+Connections+4.0+API+Documentation#action=openDocument&res_title=Constraints&content=pdcontent">Search API</a>
*/
public EntityList<Result> getResultsWithConstraint(String query, Constraint constraint) throws ClientServicesException {
List<Constraint> constraintList = new ArrayList<Constraint>();
constraintList.add(constraint);
return getResultsWithConstraint(query, constraintList,null);
}
/**
* @param query Text to search for
* @param constraints List<Constraint>
*
* @return EntityList<Scope>
* @throws ClientServicesException
*
* @see <a href="http://www-10.IBM.com/ldd/appdevwiki.nsf/xpDocViewer.xsp?lookupName=IBM+Connections+4.0+API+Documentation#action=openDocument&res_title=Constraints&content=pdcontent">Search API</a>
*/
public EntityList<Result> getResultsWithConstraint(String query, List<Constraint> constraints) throws ClientServicesException {
return getResultsWithConstraint(query, constraints,null);
}
/**
* @param query Text to search for
* @param constraints List<Constraint>
* @param parameters
*
* @return EntityList<Result>
* @throws ClientServicesException
*
*http://www-10.IBM.com/ldd/appdevwiki.nsf/xpDocViewer.xsp?lookupName=IBM+Connections+4.0+API+Documentation#action=openDocument&res_title=Constraints&content=pdcontent
*/
public EntityList<Result> getResultsWithConstraint(String query, List<Constraint> constraints, Map<String, String> parameters) throws ClientServicesException{
Map<String, List<String>> params = new HashMap<String, List<String>>();
// We can not use a map of constraints, since there could be multiple constraints but map can have only one key named constraint
List<String> formattedConstraints = generateConstraintParameter(constraints);
if(parameters == null){
parameters = new HashMap<String,String>();
}
for(Map.Entry<String, String> entry : parameters.entrySet()){
List<String> valueList = new ArrayList<String>();
valueList.add(entry.getValue());
params.put(entry.getKey(), valueList);
}
params.put("constraint", formattedConstraints);
return getResultsList(query, params);
}
/**
* Search IBM Connection for available scopes ( Applications in which search can be executed )
*
* @method getScopes
* @return EntityList<Scope>
* @throws SearchServiceException
*/
public EntityList<Scope> getScopes() throws ClientServicesException {
Map<String,String> params = new HashMap<String,String>();
String searchQry = SearchUrls.SCOPES.format(this);
return getScopeEntityList(searchQry, params);
}
/**
* Lists the elements in an Atom entry representing the result returned by a search.
*
* @param query Text to search for
* @return ResultList
* @throws ClientServicesException
*/
public EntityList<Result> getResults(String query) throws ClientServicesException {
return getResults(query, new HashMap<String, String>());
}
//------------------------------------------------------------------------------------------------------------------
// Getting Search feeds
//------------------------------------------------------------------------------------------------------------------
/**
*
* @return {IFeedHandler<Result>}
*/
public IFeedHandler<Result> getResultFeedHandler() {
return new AtomFeedHandler<Result>(this) {
@Override
protected Result entityInstance(BaseService service, Node node, XPathExpression xpath) {
return new Result(service, node, nameSpaceCtx, xpath);
}
};
}
/**
*
* @return {IFeedHandler<Scope>}
*/
public IFeedHandler<Scope> getScopeFeedHandler() {
return new AtomFeedHandler<Scope>(this) {
@Override
protected Scope entityInstance(BaseService service, Node node, XPathExpression xpath) {
return new Scope(service, node, nameSpaceCtx, xpath);
}
};
}
/**
*
* @return {IFeedHandler<FacetValue>}
*/
public IFeedHandler<FacetValue> getFacetValueFeedHandler(final String facetId) {
return new AtomFeedHandler<FacetValue>(this) {
@Override
protected XPathExpression getEntityXPath(Node node){
return FacetValueXPath.facetId.getFacetPath(facetId);
}
@Override
protected FacetValue entityInstance(BaseService service, Node node, XPathExpression xpath) {
return new FacetValue(service, node, nameSpaceCtx, xpath);
}
};
}
protected Result getResultEntity(String requestUrl, Map<String, String> parameters) throws ClientServicesException {
return (Result)getEntity(requestUrl, parameters, getResultFeedHandler());
}
protected EntityList<Result> getResultEntityList(String requestUrl, Map<String, String> parameters) throws ClientServicesException {
return getEntities(requestUrl, parameters, getResultFeedHandler());
}
protected FacetValue getFacetValueEntity(String requestUrl, Map<String, String> parameters) throws ClientServicesException {
return (FacetValue)getEntity(requestUrl, parameters, getFacetValueFeedHandler("Person"));
}
protected EntityList<FacetValue> getFacetValueEntityList(String requestUrl, Map<String, String> parameters) throws ClientServicesException {
return getEntities(requestUrl, parameters, getFacetValueFeedHandler("Person"));
}
protected Scope getScopeEntity(String requestUrl, Map<String, String> parameters) throws ClientServicesException {
return (Scope)getEntity(requestUrl, parameters, getScopeFeedHandler());
}
protected EntityList<Scope> getScopeEntityList(String requestUrl, Map<String, String> parameters) throws ClientServicesException {
return getEntities(requestUrl, parameters, getScopeFeedHandler());
}
/*
* Internal service methods
*/
private List<String> createTagConstraint(List<String> tags){
List<String> formattedTags = new ArrayList<String>();
String tagkey = "Tag/";
for (Iterator<String> iterator = tags.iterator(); iterator.hasNext();) {
String tag = iterator.next();
formattedTags.add(tagkey+tag);
}
return formattedTags;
}
/*
* Method formats the list of constraint into String as required by Search api.
*
*/
private List<String> generateConstraintParameter(List<Constraint> constraints){
List<String> formattedConstraintsList = new ArrayList<String>();
if(constraints != null){
for (Constraint constraint : constraints) {
StringBuilder formattedConstraint = new StringBuilder();
StringBuilder constraintParameter = new StringBuilder("");
constraintParameter.append("{\"type\":\"").append(constraint.getType()).append("\"");
if(StringUtil.isNotEmpty(constraint.getId())){
constraintParameter.append(",").append("\"id\":\"").append(constraint.getId()).append("\"");
}
// Extract all values
List<String> allValues = constraint.getValues();
StringBuilder values = new StringBuilder();
for (int valueCtr = 0; valueCtr< allValues.size();valueCtr++) {
String value = (String) allValues.get(valueCtr);
if(valueCtr == 0){
values.append("\"").append(value).append("\"");
}else{
values.append(COMMA).append("\"").append(value).append("\"");
}
}
constraintParameter.append(",").append("\"values\":[").append(values.toString()).append("]");
constraintParameter.append(",").append("\"exactMatch\":\"").append(constraint.isExactMatch()).append("\"");
constraintParameter.append("}");
formattedConstraint.append(constraintParameter.toString());
formattedConstraintsList.add(formattedConstraint.toString());
}
}
return formattedConstraintsList;
}
}