To demonstrate the Segment Criteria Contributor, you’ll create a contributor that segments users based on the title of Knowledge Base articles they have authored.
The first step is to make your related entity searchable through OData queries. For this purpose, you must have classes:
-
EntityModel
: represents your associated entity (in this case, theKBArticle
) with its fields of interest. -
ODataRetriever
: obtains theKBArticles
that match a given OData query.
You can view the full project on Github.
Follow the instructions below to get started.
-
Create the following packages within the module:
com.liferay.segments.criteria.extension.sample.internal.odata.retreiver
com.liferay.segments.criteria.extension.sample.internal.odata.entity
com.liferay.segments.criteria.extension.sample.internal.criteria.contributor
Excellent! You have your module ready. Next, you’ll create the entity model.
Creating the Entity Model
First, create the Entity Model for KBArticle
.
-
Inside the
...internal.odata.entity
package, create theKBArticleEntityModel
class which implementsEntityModel
:public class KBArticleEntityModel implements EntityModel { }
-
Create the key for the entity name:
public static final String NAME = "KBArticle";
-
Create the variable for the entity field map:
private final Map<String, EntityField> _entityFieldsMap;
-
Create the methods to retrieve the
KBArticleEntity
, entity map, and entity name key:public KBArticleEntityModel() { _entityFieldsMap = Stream.of( new StringEntityField("title", locale -> "titleKeyword") ).collect( Collectors.toMap(EntityField::getName, Function.identity()) ); } @Override public Map<String, EntityField> getEntityFieldsMap() { return _entityFieldsMap; } @Override public String getName() { return NAME; }
Next, you’ll create the OData Retriever.
Creating the ODataRetriever
Next, create the ODataRetriever
which gets the data using the relevant filter.
-
Inside the
...internal.odata.retreiver
package, createKBArticleODataRetriever.java
which implementsODataRetriever
:public class KBArticleODataRetriever implements ODataRetriever<KBArticle> { }
-
Add the
@Component
declaration above the class declaration:@Component( immediate = true, property = "model.class.name=com.liferay.knowledge.base.model.KBArticle", service = ODataRetriever.class )
-
Create the
@Reference
objects that you need for the Filter Parser, Knowledge Base Article Service, and OData Search Adapter:@Reference private FilterParserProvider _filterParserProvider; @Reference private KBArticleLocalService _kbArticleLocalService; @Reference private ODataSearchAdapter _oDataSearchAdapter;
-
Create and instantiate the
_entityModel
object for theKBArticle
model:private static final EntityModel _entityModel = new KBArticleEntityModel();
-
Create the public methods to retrieve the results and the results count from the OData filter:
@Override public List<KBArticle> getResults( long companyId, String filterString, Locale locale, int start, int end) throws PortalException { Hits hits = _oDataSearchAdapter.search( companyId, filterString, KBArticle.class.getName(), _entityModel, _getFilterParser(), locale, start, end); return _getKBArticles(hits); } @Override public int getResultsCount( long companyId, String filterString, Locale locale) throws PortalException { return _oDataSearchAdapter.searchCount( companyId, filterString, KBArticle.class.getName(), _entityModel, _getFilterParser(), locale); }
-
Create the private methods for instantiating the
FilterParser
and retrieving the Knowledge Base article(s) that meet the criteria:private FilterParser _getFilterParser() { return _filterParserProvider.provide(_entityModel); } private KBArticle _getKBArticle(Document document) throws PortalException { long resourcePrimKey = GetterUtil.getLong( document.get(Field.ENTRY_CLASS_PK)); return _kbArticleLocalService.getLatestKBArticle(resourcePrimKey, 0); } private List<KBArticle> _getKBArticles(Hits hits) throws PortalException { Document[] documents = hits.getDocs(); List<KBArticle> kbArticles = new ArrayList<>(documents.length); for (Document document : documents) { kbArticles.add(_getKBArticle(document)); } return kbArticles; }
You’re all set to create the Segments Criteria Contributor!
Creating the SegmentsCriteriaContributor
Now create the SegmentsCriteriaContributor
class that consumes the previous
classes to retrieve the articles that match the query generated by the criteria
editor, and contributes a query to filter users based on the articles they
authored.
-
In the
...internal.criteria.contributor
package, create aUserKBArticleSegmentCritieriaContributor
class that implementsSegmentsCriteriaContributor
.public class UserKBArticleSegmentsCriteriaContributor implements SegmentsCriteriaContributor { }
-
Create the
@Component
declaration to set properties and declare the service class.@Component( immediate = true, property = { "segments.criteria.contributor.key=" + UserKBArticleSegmentsCriteriaContributor.KEY, "segments.criteria.contributor.model.class.name=com.liferay.portal.kernel.model.User", "segments.criteria.contributor.priority:Integer=70" }, service = SegmentsCriteriaContributor.class )
-
Create the variables to enable logging, retrieve the entity model, and entity key.
private static final Log _log = LogFactoryUtil.getLog( UserKBArticleSegmentsCriteriaContributor.class); private static final EntityModel _entityModel = new KBArticleEntityModel(); public static final String KEY = "user-kb-article";
-
Create the reference variables for the OData retriever and Portal instance.
@Reference( target = "(model.class.name=com.liferay.knowledge.base.model.KBArticle)" ) private ODataRetriever<KBArticle> _oDataRetriever; @Reference private Portal _portal;
-
Create the methods to define the implementation of
SegmentsCriteriaContributor
.@Override public void contribute( Criteria criteria, String filterString, Criteria.Conjunction conjunction) { criteria.addCriterion(getKey(), getType(), filterString, conjunction); long companyId = CompanyThreadLocal.getCompanyId(); String newFilterString = null; try { StringBundler sb = new StringBundler(); List<KBArticle> kbArticles = _oDataRetriever.getResults( companyId, filterString, LocaleUtil.getDefault(), QueryUtil.ALL_POS, QueryUtil.ALL_POS); for (int i = 0; i < kbArticles.size(); i++) { KBArticle kbArticle = kbArticles.get(i); sb.append("(userId eq '"); sb.append(kbArticle.getUserId()); sb.append("')"); if (i < (kbArticles.size() - 1)) { sb.append(" or "); } } newFilterString = sb.toString(); } catch (PortalException pe) { _log.error( com.liferay.petra.string.StringBundler.concat( "Unable to evaluate criteria ", criteria, " with filter ", filterString, " and conjunction ", conjunction.getValue()), pe); } if (Validator.isNull(newFilterString)) { newFilterString = "(userId eq '0')"; } criteria.addFilter(getType(), newFilterString, conjunction); } @Override public EntityModel getEntityModel() { return _entityModel; } @Override public String getEntityName() { return KBArticleEntityModel.NAME; } @Override public List<Field> getFields(PortletRequest portletRequest) { return Collections.singletonList( new Field( "title", LanguageUtil.get(_portal.getLocale(portletRequest), "title"), "string")); } @Override public String getKey() { return KEY; } @Override public Criteria.Type getType() { return Criteria.Type.MODEL; }
After deploying your extension, the segment criteria editor includes a new
section containing Knowledge Base properties. Notice that the section’s UI,
the properties, and their associated input fields and operations have been
automatically generated based on the information provided by the extension
services. For instance, the Knowledge Base article title supports equals, not
equals, contains, and not contains operations because it was defined as a
StringEntityField
.
Figure 1: The sample field appears.
Awesome! You’ve created a Segment Criteria Contributor!