
com.adobe.granite.omnisearch.commons.AbstractOmniSearchHandler Maven / Gradle / Ivy
/*************************************************************************
*
* ADOBE CONFIDENTIAL
* __________________
*
* Copyright 2015 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
package com.adobe.granite.omnisearch.commons;
import com.adobe.granite.omnisearch.api.suggestion.PredicateSuggestion;
import com.adobe.granite.omnisearch.spi.core.OmniSearchHandler;
import com.day.cq.i18n.I18n;
import org.apache.commons.lang.StringUtils;
import org.apache.jackrabbit.api.observation.JackrabbitEventFilter;
import org.apache.jackrabbit.api.observation.JackrabbitObservationManager;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.jackrabbit.vault.util.JcrConstants;
import org.apache.sling.api.resource.ValueMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.ValueFactory;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventListener;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import java.util.Map;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
/**
* AbstractOmniSearchHandler
is an abstract class
* which other can extend to provide implemenation of OmniSearchHandler
* If any Module implements OmniSearchHandler
using AbstractOmniSearchHandler
,
* It needs to create contentNode under path METADATA_PATH. And provide all the property details in that Node.
* Properties:
*
* - IS_SUGGESTABLE_PROPERTY
- Property that decide if Predicate should be used in predicate suggestions
* - METADAT_PATH
- PATH where metadata of all the modules will be stored
* - NODE_TYPE_PROPERTY
- Resource type of Module (eg. dam:Asset)
* - OPTION_PATH_PROPERTY
- optionPath property of a Predicate
* - PREDICATE_PATH_PROPERTY
- property that stores Predicate Path in METADATA Node
* - DEFAULT_SEARCH_PATH_PROPERTY
- property that stores Default Search Path in METADATA Node
* - PREDICATE_TYPE_PROPERTY
- property that identifies type of Predicate
*
*/
public abstract class AbstractOmniSearchHandler implements OmniSearchHandler, EventListener {
protected final String IS_SUGGESTABLE_PROPERTY = "isSuggestable";
/**
* This service user has been created in GRANITE
* It has default read access to "/libs/granite/omnisearch/content/metadata" and "/libs/settings"
* If Modules are using this service user, they need to bind it their bundle and
* if their Predicate
or Option for Predicate
are not in above
* mentioned Path, Module owner need to provide access to necessary paths as done for Assets and Sits
*/
protected final String OMNI_SEARCH_SERVICE_USER = "omnisearch-service";
private final String PREDICATE_GROUP_CONSTANT = "group";
private final String PREDICATE_PROPERTY_CONSTANT = "property";
private final String PREDICATE_VALUE_CONSTANT = "value";
private final String PREDICATE_OR_CONSTANT = "p.or";
public final String METADATA_PATH = "/libs/granite/omnisearch/content/metadata";
private final String NODE_TYPE_PROPERTY ="nodeType";
private final String OPTION_PATH_PROPERTY = "optionPaths";
private final String PREDICATE_PATH_PROPERTY = "predicatePath";
private final String DEFAULT_SEARCH_PATH_PROPERTY = "defaultSearchPath";
private final String PREDICATE_TYPE_PROPERTY = "text";
private final String INCLUDE_IN_SUGGESTIONS = "includeInSuggestions";
private final String SUGGESTION_FROM_SEARCH_PATH_PROPERTY = "suggestionFromSearchPath";
private final String PREDICATE_PROPERTY_NAME = "name";
private final String LIST_ORDER = "listOrder";
private final String LOCATION_PREDICATE = "location";
private final String SEARCH_RAIL_PATH_PROPERTY = "searchRailPath";
/**
* This is resoucre type of Module that is extending
* AbstractOmniSearchHandler
* (eg. dam:Asset) for AssetOmniSearchHandler
*/
private String nodeType;
/**
* This is location of predicate for Module that is extending
* AbstractOmniSearchHandler
*/
private String _predicatePath;
/**
* This is location of search panel node for the Module
* that is extending AbstractOmniSearchHandler
*/
private String searchRailPath;
/**This is default search path for Module , It is used
* in case there was no search path provided
* from the UI to search of result data.
*
*/
private String defaultSearchPath;
/**This is a Name of OmniSearchHandler
* that will display as Module Name at UI.
*/
private String handlerName;
private Boolean includeInSuggestions = false;
private Boolean suggestionFromSearchPath = true;
private static final Logger log = LoggerFactory.getLogger(AbstractOmniSearchHandler.class);
/**
* This function returns Query
that provides suggestions
* based on parameters provide in the request. It will look for "fulltext"
* paramter in request and "fulltext" parameter will treated as search term.
* Based on this search term suggestion query will be created.
* @param resolver ResourceResolver instance
* @param searchTerm text term for which suggestions are require
* @return Query
that returns suggestions on execution
*/
public Query getSuggestionQuery(ResourceResolver resolver, String searchTerm) {
if(includeInSuggestions == null || includeInSuggestions == false) {
return null;
}
try {
//nodetype and defaultSearchPath will have to concatenated, as these varriable can not be given using bind variable
// throws ParseException, if I use bindVariable for nodetype and defaultSearchPath as well
final String descendantNodeCondition = suggestionFromSearchPath ? " AND ISDESCENDANTNODE([" + getDefaultSearchPath() + "])" : "";
final String queryStr = "SELECT [rep:suggest()] FROM [" + getResourceType() + "] as s WHERE SUGGEST($term)" + descendantNodeCondition;
final Query query = createQuery(resolver, searchTerm, queryStr);
log.debug("Suggestion query {}", query.toString());
return query;
} catch (RepositoryException e) {
log.error("Error while creating Suggestion query", e);
}
return null;
}
/**This function provide List of Predicates that matched to current
* request parameters. It will match the value of PredicateSuggestion
* to the search term. Currently predicate suggestion works only if
* length of search term is more than MIN_SUGGESTION_REQUIRE_SIZE
* @param resolver ResourceResolver instance
* @param i18n I18n instance
*@param searchTerm text term for which suggestions are require @return List
of PredicateSuggestion
*/
public List getPredicateSuggestions(ResourceResolver resolver, I18n i18n, String searchTerm) {
List matchedPredicateList = new ArrayList();
List predicateSuggestionList = getPredicateSuggestions(resolver, i18n);
if ( !predicateSuggestionList.isEmpty()) {
for( PredicateSuggestion predicateSuggestion : predicateSuggestionList) {
if (predicateSuggestion.getOptionTitle().toLowerCase().contains(searchTerm.toLowerCase())) {
if(predicateSuggestion.getQueryParameters() == null) {
predicateSuggestion.setQueryParameters(getQueryParameters(predicateSuggestion, resolver));
}
if(resolver.getResource(predicateSuggestion.getTypePath()) != null && resolver.getResource(predicateSuggestion.getOptionPath()) != null){
matchedPredicateList.add(predicateSuggestion);
}
}
}
}
return matchedPredicateList;
}
/**
* This function returns Query
that provides spell check suggestions
* based on parameters provide in the request. It will look for "fulltext"
* paramter in request and "fulltext" parameter will treated as search term.
* Based on this search term spell check query will be created.
* @param resolver ResourceResolver instance
* @param searchTerm text term for which suggestions are require
* @return Query
that returns spell check suggestion on execution.
*/
public Query getSpellCheckQuery(ResourceResolver resolver, String searchTerm) {
if(includeInSuggestions == null || includeInSuggestions == false) {
return null;
}
try {
final String queryStr = "SELECT [rep:spellcheck()] FROM [" + getResourceType() + "] as s WHERE [jcr:path] = '/' AND SPELLCHECK($term)";
final Query query = createQuery(resolver, searchTerm, queryStr);
log.debug("Spellcheck query {}", query.toString());
return query;
} catch (RepositoryException e) {
log.error("Error while creating Spellcheck query", e);
}
return null;
}
private Query createQuery(ResourceResolver resolver, String searchTerm, String queryStr) throws RepositoryException {
final Session session = resolver.adaptTo(Session.class);
final QueryManager queryManager = session.getWorkspace().getQueryManager();
final Query query = queryManager.createQuery(queryStr,Query.JCR_SQL2);
final ValueFactory vf = session.getValueFactory();
query.bindValue("term", vf.createValue(searchTerm));
return query;
}
/**
* returns Path for config content node for the Module.
* It is located under Node at METADATA_DATA
path.
* Node at this path contains information regarding Search Module
* identified by location i.e. getID()
*/
protected String getModuleConfigNodePath() {
return METADATA_PATH + "/" + getID();
}
public Resource getModuleConfig( ResourceResolver resolver) {
Resource moduleConfigResource = resolver.getResource(getModuleConfigNodePath());
if (moduleConfigResource == null) {
log.debug("no module config resource located at path {}", getModuleConfigNodePath());
}
return moduleConfigResource;
}
public PredicateSuggestion getLocationSuggestion(ResourceResolver resolver, I18n i18n, String searchTerm) {
if (i18n.get(getName()).toLowerCase().contains(searchTerm.toLowerCase())) {
Map queryParameters = new HashMap();
queryParameters.put(LOCATION_PREDICATE, getID());
PredicateSuggestion locationPredicateSuggestion = new PredicateSuggestion(LOCATION_PREDICATE, getName());
locationPredicateSuggestion.setQueryParameters(queryParameters);
return locationPredicateSuggestion;
}
return null;
}
/**This function provide a Name of OmniSearchHandler
* This will display as Module Name at UI.
* @return Name of OmniSearchHandler
*/
protected String getName() {
return handlerName;
}
/**
* This function returns the resourceType of the OmniSearchHandler
implementations
* @return resource type
*/
protected String getResourceType() {
return nodeType;
}
/**
* This function returns the resource that contains predicate list
* for the module.
*
* If a module need to support /conf usage, then this method should be overridden
*
* Default Implementation simply return resolver.getResource(getPredicatePath())
* @param resolver resolver
* @return resource
*/
protected Resource getPredicateRootResource(ResourceResolver resolver) {
return resolver.getResource(getPredicatePath());
}
/**
* This function returns the location of predicate of the Module
* that implements OmniSearchHandler
* @return predicate path
*/
protected String getPredicatePath() {
return _predicatePath;
}
/**
* This function returns the default Search Path of the Module
* that implements OmniSearchHandler
* @return predicate path
*/
protected String getDefaultSearchPath() {
return defaultSearchPath;
}
/**
* This function updates the list of PredicateSuggestion
that are available for the Module.
* This function reads the predicates from _predicatePath. and check if Predicate has IS_SUGGESTABLE_PROPERTY as true.
* In that case , it looks for options for this predicates and update list with type from PREDICATE_TYPE_PROPERTY property of
* predicate and title from jct:title property of option.
* @param resolver ResourceResolver
*/
private List getPredicateSuggestions(ResourceResolver resolver, I18n i18n) {
List predicateSuggestionList = new ArrayList();
String predicatePath = getPredicatePath();
if (StringUtils.isNotEmpty(predicatePath)) {
Resource predicateRoot = getPredicateRootResource(resolver);
if (predicateRoot == null) {
log.warn("Non-Existent Predicate Path " + predicatePath);
return Collections.emptyList();
}
extractPredicateSuggestions(resolver, predicateSuggestionList, predicateRoot, i18n);
} else {
log.debug("Invalid Predicate Path {} skipping predicate suggestion loading ", predicatePath);
}
return predicateSuggestionList;
}
private void extractPredicateSuggestions(ResourceResolver resolver, List predicateSuggestionList, Resource predicateRoot, I18n i18n) {
for (Resource predicateResource : predicateRoot.getChildren()) {
ValueMap vm = predicateResource.adaptTo(ValueMap.class);
boolean isSuggestable = vm.get(IS_SUGGESTABLE_PROPERTY, false);
String optionsPath = vm.get(OPTION_PATH_PROPERTY, String.class);
String predicateType = i18n.getVar(vm.get(PREDICATE_TYPE_PROPERTY, String.class));
if (isSuggestable && optionsPath != null && predicateType != null) {
Resource optionsRes = resolver.getResource(optionsPath);
if (optionsRes != null) {
for (Resource resource : optionsRes.getChildren()) {
ValueMap optionProps = resource.adaptTo(ValueMap.class);
String predicateTitle = i18n.getVar(optionProps.get(JcrConstants.JCR_TITLE, String.class));
if (predicateTitle != null) {
PredicateSuggestion currPredicateSuggestion = new PredicateSuggestion(predicateType, predicateTitle, predicateResource.getPath(), resource.getPath());
currPredicateSuggestion.setQueryParameters(getQueryParameters(currPredicateSuggestion, resolver));
predicateSuggestionList.add(currPredicateSuggestion);
}
}
}
}
extractPredicateSuggestions(resolver, predicateSuggestionList, predicateResource, i18n);
}
}
/**This function add query parameters to the PredicateSuggestions
which
* UI will add in the search query or URL.
* Currently this method create and add, query parameters to the Predicates
of metatype "option" or
* "listoption".
* if any module has Predicates
into their PredicateSuggestion
list which are not of type "option" or "listoption",
* module owner should override this method in their OmniSearchHandler
implementation to meet their requirement.
* @param predicateSuggestion PredicateSuggestion
for which query parameters needed be add
* @param resolver ResourceResolver
instance
* @return a map
*/
protected Map getQueryParameters(PredicateSuggestion predicateSuggestion, ResourceResolver resolver) {
Map queryParameters = new HashMap();
Resource predicateResource = resolver.getResource(predicateSuggestion.getTypePath());
ValueMap vm = predicateResource.adaptTo(ValueMap.class);
String listOrder = vm.get(LIST_ORDER, String.class);
String propertyType = vm.get(PREDICATE_PROPERTY_NAME, String.class);
Resource optionResource = resolver.getResource(predicateSuggestion.getOptionPath());
List valueList = getOptionValuesList(optionResource);
if(valueList.isEmpty()) {
return null;
}
if(valueList.size() > 1) {
//groupPredicate
String predicateGroupName = PREDICATE_GROUP_CONSTANT;
if (listOrder != null) {
predicateGroupName = listOrder + "_" + predicateGroupName;
}
String predicateProperty = predicateGroupName + "." + PREDICATE_PROPERTY_CONSTANT;
queryParameters.put(predicateProperty, propertyType);
int valueCount = 0;
for(String value : valueList) {
valueCount++;
queryParameters.put(predicateProperty + "." + valueCount + "_" + PREDICATE_VALUE_CONSTANT, value);
}
queryParameters.put(predicateGroupName + "." + PREDICATE_OR_CONSTANT,"true");
} else {
String predicateProperty = PREDICATE_PROPERTY_CONSTANT;
if(listOrder != null) {
predicateProperty = listOrder + "_" + predicateProperty;
}
queryParameters.put(predicateProperty, propertyType);
queryParameters.put(predicateProperty + "." + PREDICATE_VALUE_CONSTANT, valueList.get(0));
}
return queryParameters;
}
private List getOptionValuesList(Resource optionResource) {
List valueList = new ArrayList();
if(optionResource != null) {
ValueMap optionResValueMap = optionResource.getValueMap();
String optionvalue = optionResValueMap.get("value",String.class);
if(!"".equals(optionvalue) && optionvalue != null) {
valueList.add(optionvalue);
}
}
Iterator childRes = optionResource.getChildren().iterator();
while (childRes.hasNext()) {
Resource child = childRes.next();
valueList.addAll(getOptionValuesList(child));
}
return valueList;
}
/**
* This function updates all the properties of AbstractOmniSearchHandler
* @param resolver ResourceResolver
instance
*/
private void updateAllProperties(ResourceResolver resolver) {
Resource configResource = getModuleConfig(resolver);
if (configResource != null){
log.debug("reading configuration from {}", configResource.getPath());
ValueMap vm = configResource.adaptTo(ValueMap.class);
handlerName = vm.get(JcrConstants.JCR_TITLE, "");
_predicatePath = vm.get(PREDICATE_PATH_PROPERTY, "");
defaultSearchPath = vm.get(DEFAULT_SEARCH_PATH_PROPERTY, "");
nodeType = vm.get(NODE_TYPE_PROPERTY, "");
includeInSuggestions = vm.get(INCLUDE_IN_SUGGESTIONS, Boolean.class);
suggestionFromSearchPath = vm.get(SUGGESTION_FROM_SEARCH_PATH_PROPERTY, true);
searchRailPath = vm.get(SEARCH_RAIL_PATH_PROPERTY, "");
if ( !StringUtils.isEmpty(searchRailPath)) {
Resource searchPanelResource = resolver.getResource(searchRailPath);
if (searchPanelResource != null) {
ValueMap searchProperties = searchPanelResource.adaptTo(ValueMap.class);
_predicatePath = searchProperties.get(PREDICATE_PATH_PROPERTY, "");
}
}
invalidated();
}
}
/**
* This function clears the List
of PredicateSuggestion
*/
private void invalidated() {}
/**
* This function updates the contentNodePath.
* It also updates all the properties of AbstractOmniSearchHandler
* It also apply a event at Node at contentNodePath and _predicatePath so that
* at any change on this location will triggers all the update again.
* It usually called on activation of OmniSearchHandler
or on event from
* implementation of OmniSearchHandler
* @param resolver ResourceResolver
instance
*/
public void init(ResourceResolver resolver) {
Session session = resolver.adaptTo(Session.class);
try {
String moduleConfigPath = getModuleConfigNodePath();
log.debug("calling init for {}", moduleConfigPath);
JackrabbitEventFilter eventFilter = new JackrabbitEventFilter()
.setAbsPath(moduleConfigPath)
.setEventTypes(Event.NODE_ADDED|Event.NODE_REMOVED|Event.PROPERTY_CHANGED|Event.PROPERTY_ADDED|Event.PROPERTY_REMOVED)
.setIsDeep(true)
.setNoLocal(true);
JackrabbitObservationManager observationManager = (JackrabbitObservationManager) session.getWorkspace()
.getObservationManager();
updateAllProperties(resolver);
observationManager.addEventListener(this, eventFilter);
} catch (RepositoryException e) {
log.error("Error while initializing ", e);
}
}
/**
* This function clear the predicateSuggestionList and remove all the eventListeners.
* This is usually called on deactivation of OmniSearchHandler
* @param resolver ResourceResolver
instance
*/
public void destroy(ResourceResolver resolver) {
invalidated();
Session session = resolver.adaptTo(Session.class);
try {
if (session != null) {
session.getWorkspace().getObservationManager().removeEventListener(this);
}
resolver.close();
} catch (RepositoryException re) {
log.error("Error while deactivating the OmniSearchHandler", re);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy