Search Queries and Filters

To get sensible results from the search engine, you must provide a sensible query. Liferay’s query APIs are in the portal-search-api module and are used to construct both queries and filters.

Filters ask a yes or no question for every search document and do not calculate relevance. A filter might ask is the status field equal to staging or live?

Queries ask the same yes or no question compute how well a document matches the specified criteria. This is the concept of relevance scoring. A query might ask Does the document’s content field contain the words “Liferay”, “Content”, or “Management”, and how relevant is the content of the document to the searched keywords?

To use queries and filters in Liferay, construct the query and then add it to the request as a query or filter:

  1. Add a query to the request with SearchRequestBuilder.query(fooQuery).

  2. To filter instead of querying, add it to the search request with SearchRequestBuilder.postFilterQuery(fooQuery).

Here you can deploy, test, and inspect a Gogo Shell command that queries the company index.

Tip

While developing, it can be helpful to inspect the query as generated by the search engine. See Inspecting any Liferay-Generated Elasticsearch Query for information.

Deploy A Gogo Shell Command with Custom Search Queries

Start a new Liferay instance by running

docker run -it -m 8g -p 8080:8080 liferay/portal:7.4.3.112-ga112

Sign in to Liferay at http://localhost:8080. Use the email address test@liferay.com and the password test. When prompted, change the password to learn.

Then, download the project and complete some prerequisites:

  1. Download and unzip the liferay-b9f3 project:

    curl https://resources.learn.liferay.com/dxp/latest/en/using-search/developer-guide/liferay-b9f3.zip -O
    
    unzip liferay-b9f3.zip
    
  2. From the module root, build and deploy.

    ./gradlew deploy -Ddeploy.docker.container.id=$(docker ps -lq)
    
    Note

    This command is the same as copying the deployed jars to /opt/liferay/osgi/modules on the Docker container.

  3. Confirm the deployment in the Liferay Docker container console.

    STARTED com.acme.b9f3.impl_1.0.0 [1775]
    
  4. The B9F3 sample includes a shell script that loads documents and content into Liferay. To run it, first retrieve the default site’s ID and the global site’s ID from the running Liferay instance.

    In the default site, open Site Menu (Site Menu) → ConfigurationSite Settings.

  5. Go to Site Configuration and record the Site ID.

  6. Go to the Global site. Click Site Selector (Site Selector) → Available SitesGlobal.

  7. Find and record the Site ID for the Global site.

  8. To run the script, go to liferay-b9f3/curl in the terminal.

  9. Run the shell script with the site IDs as arguments:

    ./populate_b9f3.sh [default site ID] [global site ID]
    

    For example,

    ./populate_b9f3.sh 20116 20119
    

    The script adds these resources:

    • Web Content:

      • Able Content
      • Able Content Folder
        • Able Content
    • Document and Media:

      • Able Document
      • Able Document Folder
        • Able Document

Test the B9F3 Search Queries

  1. On the locally running Liferay’s home page, enter able into the search bar. Six results appear.

  2. Open the Global Menu (Global Menu), navigate to Control PanelGogo Shell.

  3. Enter b9f3:search able and answer the CAPTCHA challenge.

  4. Click Execute.

    You can execute the search query from the Gogo shell.

  5. Four results appear, all of them root content. Anything nested inside the created folders does not have a folderId of 0, and is not returned. However, the folders themselves are returned, because they are contained in the root folder and given a folderId of 0.

Tip

The root folder in the Documents and Media and Web Content applications have a folderId of 0. You can check the indexed folder ID of each search result by enabling Display Results in Document Form in the Search Results widget. See Inspecting Search Engine Documents for more information.

Understanding the B9F3 Search Queries

First, initialize a SearchRequestBuilder. You can use this object to construct the search request.

SearchRequestBuilder searchRequestBuilder =
	_searchRequestBuilderFactory.builder();

Next, populate the SearchContext within the request. This sets the entry class names to search and the company ID of the instance to be searched. It also sets the keywords to search. These keywords are entered by the user at search time.

Note

You must either set keywords into the search context or enable empty search in the request builder with searchRequestBuilder.emptySearchEnabled(true);.

searchRequestBuilder.withSearchContext(
	searchContext -> {
		searchContext.setCompanyId(_portal.getDefaultCompanyId());
		searchContext.setEntryClassNames(
			new String[] {
				"com.liferay.document.library.kernel.model.DLFileEntry",
				"com.liferay.document.library.kernel.model.DLFolder",
				"com.liferay.journal.model.JournalArticle",
				"com.liferay.journal.model.JournalFolder"
			});
		searchContext.setKeywords(keywords);
	});

Now fashion the query clauses. This example nests two MUST query clauses inside a Boolean query: one clause is a term query for matching the folderId field to the value 0, and the other is for performing a full text match query of the user’s search keywords to the localized title field.

BooleanQuery booleanQuery = _queries.booleanQuery();

booleanQuery.addMustQueryClauses(
	_queries.term(Field.FOLDER_ID, "0"),
	_queries.match(
		StringBundler.concat(
			"localized_", Field.TITLE, StringPool.UNDERLINE,
			LocaleUtil.US),
		keywords));

Add the Boolean query with its nested clauses to the request, execute the request, then process the response as needed. This prints the document’s uid field and its score.

SearchRequest searchRequest = searchRequestBuilder.query(
	booleanQuery
).build();

SearchResponse searchResponse = _searcher.search(searchRequest);

SearchHits searchHits = searchResponse.getSearchHits();

for (SearchHit searchHit : searchHits.getSearchHits()) {
	Document document = searchHit.getDocument();

	String uid = document.getString(Field.UID);

	System.out.println(
		StringBundler.concat(
			"Document ", uid, " has a score of ",
			searchHit.getScore()));
}

These Liferay services are referenced in the B9F3 code:

@Reference
private Portal _portal;

@Reference
private Queries _queries;

@Reference
private RoleLocalService _roleLocalService;

@Reference
private Searcher _searcher;

@Reference
private SearchRequestBuilderFactory _searchRequestBuilderFactory;

@Reference
private UserLocalService _userLocalService;

Implementing Nested Queries

To create queries for object fields, web content structure fields, or document metadata sets, you must query the field according to its nested structure using a nested query. Inside the query, specify the path (e.g., ddmFieldArray for web content and document metadata sets, nestedFieldArray for objects) and create a Boolean query with two clauses that use dot notation: one clause matches the field name, and the other matches the value (e.g., the user’s keywords).

Querying Web Content Structure Fields

Web content structures and documents and media metadata sets are indexed similarly. A web content structure field is indexed like this:

{
   ddmFieldName=ddm__text__35174__Text25689566_en_US,
   ddmFieldValueText_en_US_String_sortable=able text,
   ddmValueFieldName=ddmFieldValueText_en_US,
   ddmFieldValueText_en_US=Able text
}

In Elasticsearch’s query syntax you might create a query like this for the field:

{
  "nested": {
    "path": "ddmFieldArray",
    "query": {
      "bool": {
        "must": [
          {
            "match": {
              "ddmFieldArray.ddmFieldName": "ddm__text__35174__Text25689566_en_US"
            }
          },
          {
            "match": {
              "ddmFieldArray.ddmFieldValueText_en_US": "${keywords}"
            }
          }
        ]
      }
    }
  }
}

You can create the same query in Liferay’s Java search API:

BooleanQuery booleanQuery = queries.booleanQuery();

MatchQuery fieldNameQuery = queries.match("ddmFieldArray.ddmFieldName", "ddm__text__35174__Text25689566_en_US");

MatchQuery fieldValueQuery = queries.match("ddmFieldArray.ddmFieldValueKeyword_en_US", keywords);

booleanQuery.addMustQueryClauses(fieldNameQuery, fieldValueQuery);

NestedQuery nestedQuery = queries.nested("ddmFieldArray", booleanQuery);

Querying Object Fields

An object’s text field is indexed like this:

[
   {
      fieldName=fooText,
      value_en_US=Able Text,
      valueFieldName=value_en_US
   },
   {
      fieldName=fooText,
      value_keyword_lowercase=Able Text,
      valueFieldName=value_keyword_lowercase
   }
]

In Elasticsearch’s query syntax you might create a query like this for the field:

{
  "nested": {
    "path": "nestedFieldArray",
    "query": {
      "bool": {
        "must": [
          {
            "match": {
              "nestedFieldArray.fieldName": "fooText"
            }
          },
          {
            "match": {
              "nestedFieldArray.value_en_US": "${keywords}"
            }
          }
        ]
      }
    }
  }
}

You can create the same query in Liferay’s Java search API:

BooleanQuery booleanQuery = queries.booleanQuery();

MatchQuery fieldNameQuery = queries.match("nestedFieldArray.fieldName", "fooText");

MatchQuery fieldValueQuery = queries.match("nestedFieldArray.value_en_US", keywords);

booleanQuery.addMustQueryClauses(fieldNameQuery, fieldValueQuery);

NestedQuery nestedQuery = queries.nested("nestedFieldArray", booleanQuery);

Filtering

There’s no separate API for filtering. Construct the query as usual and add it to the search request, specifying it as a filter using the SearchRequestBuilder.postFilterQuery(fooQuery) method.

For example, you can change the B9F3 code to first filter the documents with the folderId of 0 and then perform the match query on the keywords and title field:

TermQuery termQuery = _queries.term(Field.FOLDER_ID, "0");

searchRequestBuiler.postFilterQuery(termQuery);

MatchQuery matchQuery = _queries.match(
    StringBundler.concat(
        "localized_", Field.TITLE, StringPool.UNDERLINE,
           LocaleUtil.US), keywords);

searchRequestBuilder.query(matchQuery);

SearchRequest searchRequest = searchRequestBuilder.build();

Capabilities

Product

Contact Us

Connect

Powered by Liferay
© 2024 Liferay Inc. All Rights Reserved • Privacy Policy