Starting in Niagara 4, a new SearchService
has been added which allows searching for Entities within selectable scopes (ie. spaces) of a station. The Search API is a simple layer on top of Niagara’s query APIs.
SearchService Properties -
In order to invoke a search, the SearchService
must be installed on a station. Here is a complete list and description of all of the properties and actions on the SearchService
:
Properties:
defaultScheme (default value: “neql”): This is the default query scheme (Ord Scheme) that is assumed when the scheme is omitted from the search query in a search request.
defaultScopes (default value: BVector
containing the “station:” and “sys:” spaces as the default scopes): The list of scopes designated as suggested defaults. These scopes will be marked as defaults in the list returned by the getSearchScopes()
action (used to supply default scope selections for clients). Each element in the list is expected to be either a BSearchScope
or a BOrd
.
maxConcurrentSearches (default value: 50): This size determines the maximum number of concurrent (active) searches that are allowed at one time. Active search tasks are those that are in a subscribed state.
maxResultsPerSearch (default value: 500): For each search request submitted, this size determines the maximum number of results that will be cached in memory for the duration of the search task.
searchTaskTimeToLive (default value: 2 minutes): For each Search Task child created in the activeSearchContainer
as the result of a search invocation, this time to live value determines how long the Search Task will linger in the station before it is automatically removed if it has not been used during this time. If the Search Task is subscribed or accessed in any way, it will reset the expiration such that the Search Task will remain until it is no longer used and this time to live has expired since it was last in a subscription state or otherwise accessed.
activeSearchContainer (hidden BVector
frozen property): A container for the active search tasks. We need this container because the active search tasks will linger in memory as long as they are subscribed, so we don’t want views on the SearchService
(such as the property sheet) causing the active search tasks to remain subscribed even if a user isn’t actively viewing the search results for a given search task.
Actions
search (available to operator users, parameter is a BSearchParams
, result is a BOrd
to the BSearchTask
created): Performs an asynchronous search for data based on user specified search parameters. Search results are returned via an Ord
that maps to a BSearchTask
which you can subsequently use when forming the parameter for the retrieveResults
action to read back search results from the task.
retrieveResults (available to operator users, parameter is a BResultsRequest
, result is a BSearchResultSet
): Used to retrieve search results from a previously submitted search. When forming the BResultRequest
argument, you should use the BOrd
of the search task you received from the search action, and then you can also define the starting index for search results to retrieve, as well as how many (max results) to retrieve.
getSearchScopes (available to operator users, result is a BVector
of BSearchScopes
): Used to retrieve the available search scopes based on which scopes have registered QueryHandlers
and/or SearchProviders
.
updateDefaultScopeInfo (async action, no args): Updates the default name and lexicon info for any blank values in the default scopes.
Invoking a search from the SearchService
Invoking a search and retrieving results is handled through actions on the SearchService
. Here is a simple example:
Simple Search Example
BSearchService searchService = BSearchService.getService(); // Only works on station side, otherwise use service ord scheme to lookup search service
BOrd searchRequest = BOrd.make("neql:n:point"); // the query
BSearchParams searchParams = new BSearchParams(searchRequest, /*scopes*/Sys.getStation());
BOrd searchTaskOrd = searchService.search(searchParams); // Invokes a search and gives you back an ORD to the BSearchTask
// Retrieve all results
BSearchResultSet results = null;
while (results == null || !results.getResultsComplete())
{
Thread.sleep(100); // Give the search some time to complete
// This code asks for all available results at this time, but you could ask for
// chunks of results by adjusting the startIndex and maxResults arguments
BResultsRequest resultsRequest = BResultsRequest.make(searchTaskOrd, /*startIndex*/0, /*maxResults*/-1);
results = searchService.retrieveResults(resultsRequest);
}
// Now that we have all of the results, we can do something with them
results.streamResults().forEach(result -> System.out.println("Found a search result: " + ((BSearchResult)result).getOrd()));
Implementing a SearchProvider
By default, the SearchService
is intended to work with QuerySchemes
that have registered QueryHandlers
. This covers common cases for search providers, such as neql. However, there may be other OrdSchemes that can provide search results, such as BQL. In those cases, you can create a BISearchProvider
implemention. Here is an excerpt from the BISearchProvider
interface.
BISearchProvider
/**
* A search provider can be used as an alternative to a query scheme to
* allow for searching spaces in Niagara. A search provider must be
* registered as an agent on any BOrdSchemes that it supports AND it
* must also register as an agent on any scopes (ie. spaces) that it
* supports searching.
*/
public interface BISearchProvider extends BIAgent
{
/**
* Search the specified scope with the given query ORD.
* When called by a search task via the SearchService, this method
* is called on an async executor, so you can do the work on the
* calling thread.
*
* @param queryOrd The query ORD to resolve against the given scope
* @param scope The scope to resolve the query ORD against
* @param context The context associated with this search request. Implementers
* should extract any user information from this context in order
* to filter results to only those permitted to the user.
*/
Stream<Entity> search(BOrd queryOrd, BIObject scope, Context context);
}
So to create your own search provider, you simply need to be able to stream Entities back in response to a query Ord for a scope. The context is also important for enforcing user permissions on results. For an example implementation of BISearchProvider
, see BBqlSearchProvider
.
Copyright © 2000-2019 Tridium Inc. All rights reserved.