« Back to Custom fields

Search for objects by custom attributes

One of the top features on Liferay 6 is the possibility to create custom attributes for all Liferay objects like Users, Organizations, Pages, etc. This functionality give you a limitless power to enhance existing objects in Liferay with your custom attributes symbolizing your custom properties. 

In this article I will use a term "Liferay Object" to mean a classic Liferay Model object such as a User, Organization or Group. So we have the possibility to create custom attributes on any object in Liferay. Cool. But even more cool is the possibility to search for this attributes. Once a custom attribute is created and an option "searchable" is selected, we can search for objects that have this custom attribute. Nice, isn't it ? In this topic I would like to show, how is possible to search for objects based on  custom attribute value.

So let's start. As the Expando mechanism in Liferay is done in a generic way, at the beginning of the search, we dont know which objects should we expect as results. Since any object in Liferay can have a custom attribute, the result can consist of different objects like Users, Organizations etc. That's why the search results will not be directly the objects (Users,Organizations) , but so called Expando Values. Expando Value is also an object within Liferay symbolizing 1 unique value of custom attribute belonging to 1 concrete object in Liferay. So if we have custom attribute "Height" defined for object User and we have a user "John" in our system with this attribute set to "180", then the Expando Value is 180. The Expando value "180" itself hold informations, which help us to track , that this value was set for custom attribute "Height" and that this value belongs to user "John". The complexity of the whole Expando system is pretty high, but it allows you to have this flexibility. More infos about Expanod can be found in different Wikis and Blogs, for example : http://www.liferay.com/web/raymond.auge/blog/-/blogs/715049.

So far to theory, now let's continue with our search.Let's image a scenario, that we want so search for all users in our system, that are exactly 180 cm (or 5' 11" for our imperial friends) high. As i previously mention, we have a custom attribute for it and all users have set some value for this attribute. So what we definitelly need to know in order to search by custom attribute, is the custom attribute name and value. So let's define two variables (customAttributeName - that will store the name of the custom attribute, which we want to search for and customAttributeValue - that will store the concrete value of custom attribute that we want to search for) for it and assign them a value.

String customAttributeName="height"

String customAttributeValue="180"

What we also need to specify is the type of Object, that we want to search for. As I have previosly mentioned there can be different objects having this custom attribute, that's why we need to specify the Object by providing classNameId of the Object. We also define the companyId, which symbolizes the server instance, within we want to search (in this case is the default one).

long classNameId = ClassNameLocalServiceUtil.getClassNameId(User.class);
long companyId = PortalUtil.getDefaultCompanyId();

So what we did is, we have called the ClassNameLocalServiceUtil for getting the class id parameter of class User, as we want to search for User objects.

Finally, we have all informations needed to trigger search. So here's the magic :

List<ExpandoValue> values = ExpandoValueLocalServiceUtil.getColumnValues(companyId, classNameId, ExpandoTableConstants.DEFAULT_TABLE_NAME, customAttributeName,customAttributeValue, -1, -1);

This is all we need to do. The last two parameters can be used for specifying search start and end within results. In this case we have set both parameters to -1, so we get all results at one time. What we have now is a list of ExpandoValues objects. As I mentioned earlier, these objects are not directly the User object, that we are looking for, but just expando values, from which we can get the user objects. What we need to do know, is extract the information about corresponding users from ExpandoValue objects. If we take a look at the ExpandoValue object, there is a method getClassPK(), which is a primary key of the target object, in our case for User object. Each liferay object has its primary key, symbolizing one and unique Object. This primary key or unique identificator is always accessible by method getPrimaryKey() or by get<ObjectName>Id() method.  Lets have some examples.

//get primary key of User object (userId)
user.getUserId();  or
user.getPrimaryKey();

//get primary key of Organization object (organizationId)
organization.getOrganizationId(); or
organization.getPrimaryKey();

//get primary key of Group
group.getGroupId(); or
group.getPrimaryKey();

So what we need to do know, is find for earch ExpandoValue object corresponding User object. We can do it in a iteration with following snippet of code.

//create an arraylist to store user objects
List<User> users = new ArrayList<User>();
//temp user object
User user;       

//iterate through list of ExpandoValues and for each
// element try to find corresponding user object
for (int i = 0; i < values.size(); i++) {
  long userId = values.get(i).getClassPK();
  try{
    user =  UserLocalServiceUtil.getUser(userId);
    users.add(user)
  }catch(NoSuchUserException e ){ 
     ////user with this primary key was not found in DB .....           
  }//

At the end we have a List with User objects. Which objects are in this List ? All User objects, that have custom attribute "height" with value "180", so all users that are 180 cm high. That's it!

Note the try-catch block. It is possible to have ExpandoValue objects that point to non-existing Users or other objects. In this case you should implement a try-catch mechanism to prevent errors.

So, hopefully it was not complicated for a start. With some refactoring, you can create a generic method, that will take also a parameter with classnameId, so you can easily look for any kind of Object.

Enjoy!

0 Attachments
23664 Views
Average (4 Votes)
The average rating is 4.5 stars out of 5.
Comments
Threaded Replies Author Date
This is really useful information. Thanks for... Jignesh Vachhani June 12, 2011 11:54 PM
Thanks for sharing, I managed to get search... Rares Barbantan July 8, 2011 1:18 AM
Hi Rares, I'm glad that this entry helped you.... Jan Gregor July 19, 2011 7:20 AM
Hi! Thank you for this article, helped a lot! ... Drew Poggemann November 21, 2011 2:38 PM
totally unproductive. How about the... Kurt Xu April 23, 2013 9:15 AM
This helped me so much thanks a million :), i... Fabian Leonardo Lopez December 1, 2013 7:17 PM

This is really useful information.
Thanks for sharing
Posted on 6/12/11 11:54 PM.
Thanks for sharing, I managed to get search working, but only if using :

long userId = values.get(i).getClassPK();

It seems that the user's id is being held in the classPK column.
Posted on 7/8/11 1:18 AM.
Hi Rares,

I'm glad that this entry helped you. In fact, the getClassPK is also just the primary key of the User object, which is the same as the keys mentioned in this article.
Posted on 7/19/11 7:20 AM in reply to Rares Barbantan.
Hi! Thank you for this article, helped a lot! We also got stuck on the:
values.get(i).getPrimaryKey();

This does not return the user primary key but I think the primary key to the expando record. We needed to change to the getClassPK() and it worked perfectly. You may want to change the article so others don't hit the same issue.

Thanks much!

Drew
Posted on 11/21/11 2:38 PM.
totally unproductive. How about the performance? Every expando value searched cause a query for user. And how about comparison operation, say query users with a height greater than 180?
Posted on 4/23/13 9:15 AM.
This helped me so much thanks a million emoticon, i did basically the same but used a dynamic query on the userId field for all iterated expando values to fetch all users at once.

Hope it helps someone.

DynamicQuery query = UserLocalServiceUtil.dynamicQuery();
query.add(PropertyFactoryUtil.forName("userI­d").in(expandoValueUserIds));
try {
foundUsers.addAll(UserLocalServiceUtil.dynamicQuery(query));
} catch (SystemException e) {
.................
}
Posted on 12/1/13 7:17 PM.