Liferay Developer Conference and Unconference 2015 were simply great. Lots of sessions, games and chances to meet some of the most active Liferay developers. It was also very exciting and encouraging to register the attention that Audience Targeting has drawn among developers in just one year. Both the session and the workshop led by Julio, Pavel and me were packed and we hope that you enjoyed them as much as we did.
Previously on Integrating Audience Targeting with your Apps…
A couple of weeks ago we started a series of blog entries about the integrability features included in Audience Targeting 2.0. As you may recall, in the first entry we compared Audience Targeting with a machine that helped you to move users from one state to other. This machine consists of three processes: classification, targeting and monitoring.
User classification based on 3rd party information: Rules as integration points
In this entry we will focus on the first process of our Audience Targeting machine: user classification. User classification is based on rules that evaluate if a certain user attribute matches a given configuration. Audience Targeting provides out of the box a number of rules that evaluate in-Liferay information such as user profile attributes or session attributes, but also information from external sources such as Facebook.
Therefore, rules are a very powerful integration point since they allow to classify users based on information provided by any external app that exposes an API. As promised, this time we’ll show some code that you can take as example for your own experiments.
Segmenting users by their influence in Twitter
Imagine that your company wants to increase the impact of a certain topic on the social networks. A good start would be to find the most influent users (e.g. in terms of connections) and have them talking about your topic. In other words, you need a rule that evaluates if the user’s influence is above a certain threshold, so that you can create user segments with the most influent users.
Most popular social networks offer APIs and documentation for developers to obtain information from a user’s profile. In this example we’ll use the Twitter API to obtain the number of followers of the current user (if he/she has filled the Twitter profile field in the Liferay user profile). Admins will be able to set a threshold that determines if the user is influent or not based on the number of followers.
Setting up the environment
Creating the rule project
Audience Targeting SDK is basically an extension of the Liferay Plugins SDK. If you have ever worked with it, the steps will sound familiar. Let’s start by creating a new rule project with these very simple commands:
at-sdk-dir>./create_rule.sh twitter-sample “Twitter Sample”
You should now find a
rule-twitter-sample folder in your SDK. It contains the skeleton of our rule project. Move to that directory and execute this command:
at-sdk-dir/rule-twitter-sample>ant deploy
Now your rule has been deployed in your Liferay bundle and you will find it in the list of rule (under the
Sample category) when editing a user segment in Audience Targeting:
Customizing the rule
It’s been quite easy to add a new rule to Audience Targeting, but so far it’s nothing but a dummy box in our user segment builder. It’s time to implement our user classification project based on Twitter followers.
Let’s start by changing some visual aspects such as the icon or the category:
TwitterSampleRuleCategory.java:
package com.liferay.content.targeting.rule.twitter.sample;
import com.liferay.content.targeting.api.model.BaseRuleCategory;
import com.liferay.content.targeting.api.model.RuleCategory;
import org.osgi.service.component.annotations.Component;
/**
* @author Eduardo Garcia
*/
@Component(immediate = true, service = RuleCategory.class)
public class TwitterSampleRuleCategory extends BaseRuleCategory {
public static final String KEY = "twitter";
@Override
public String getCategoryKey() {
return KEY;
}
@Override
public String getIcon() {
return "icon-twitter";
}
}
TwitterSampleRule.java
...
@Override
public String getIcon() {
return "icon-twitter";
}
@Override
public String getRuleCategoryKey() {
return TwitterSampleRuleCategory.KEY;
}
...
With this our rule looks more “Twitter”.
Now since we want our rule to accept a threshold value for user classification, we’ll customize the UI of our rule this way:
ct_fields.ftl
...
<@liferay_ui["message"] arguments=selectorField key="users-that-have-more-than" />
<@aui["input"] inlineField=true label="" name="followersThreshold" style="margin-bottom: 0; width: auto;" suffix="followers" title="number-of-followers" type="text" value=followersThreshold>
<@aui["validator"] name="number" />
</@>
TwitterSampleRule.java
...
@Override
public String processRule(
PortletRequest request, PortletResponse response, String id,
Map<String, String> values) {
int followersThreshold = GetterUtil.getInteger(
values.get("followersThreshold"));
JSONObject jsonObj = JSONFactoryUtil.createJSONObject();
jsonObj.put("followersThreshold", followersThreshold);
return jsonObj.toString();
}
@Override
protected void populateContext(
RuleInstance ruleInstance, Map<String, Object> context,
Map<String, String> values) {
int followersThreshold = 0;
if (!values.isEmpty()) {
followersThreshold = GetterUtil.getInteger(
values.get("followersThreshold"));
}
else if (ruleInstance != null) {
String typeSettings = ruleInstance.getTypeSettings();
try {
JSONObject jsonObj = JSONFactoryUtil.createJSONObject(
typeSettings);
followersThreshold = GetterUtil.getInteger(
jsonObj.getInt("followersThreshold"));
}
catch (JSONException jse) {
}
}
context.put("followersThreshold", followersThreshold);
}
...
Notice that the
processRule and
populateContext methods simply store/read the value of the threshold field. Don’t forget to add your keys and translations to the
Language.properties files.
So far so good but, where’s the Twitter-integration logic? The classification algorithm of Audience Targeting rules is defined in the
evaluate method of the Rule.class. Here is where we take the context (request) and the rule configuration (stored in the RuleInstance object) and determine if the current user (represented by the AnonymousUser object) matches or not the rule.
Consuming the Twitter API
This is how the evaluate method of our rule looks with the Twitter-integration logic:
TwitterSampleRule.java
...
@Override
public boolean evaluate(
HttpServletRequest request, RuleInstance ruleInstance,
AnonymousUser anonymousUser)
throws Exception {
User user = anonymousUser.getUser();
if (user == null) {
return false;
}
Contact contact = user.getContact();
String twitterScreenName = contact.getTwitterSn();
if (Validator.isNull(twitterScreenName)) {
return false;
}
JSONObject jsonObj = JSONFactoryUtil.createJSONObject(
ruleInstance.getTypeSettings());
int followersThreshold = jsonObj.getInt("followersThreshold");
ConfigurationBuilder cb = new ConfigurationBuilder();
cb.setDebugEnabled(true);
cb.setOAuthConsumerKey(_CONSUMER_KEY);
cb.setOAuthConsumerSecret(_CONSUMER_SECRET);
cb.setOAuthAccessToken(_ACCESS_KEY);
cb.setOAuthAccessTokenSecret(_ACCESS_SECRET);
try {
TwitterFactory twitterFactory = new TwitterFactory(cb.build());
Twitter twitter = twitterFactory.getInstance();
IDs followerIDs = twitter.getFollowersIDs(
twitterScreenName, -1, followersThreshold);
long[] ids = followerIDs.getIDs();
if (followersThreshold == ids.length) {
return true;
}
}
catch (TwitterException te) {
_log.error("Cannot retrieve data from Twitter", te);
}
return false;
}
...
First we discard non registered users (they don’t have a Liferay profile we can get the Twitter profile from). Then, we discard registered users without a Twitter profile. After that we access the Twitter API with our credentials and obtain the number of followers for the user’s profile. Finally, we compare this number with the threshold stored in the rule configuration and return true if it’s equal or greater*, or false otherwise.
*Notice that the third parameter getFollowersIDs is the limit to the results and has been set to the threshold value. Thus, it’s enough if the result is equal to the threshold (greater than would return the same).
Setting rule dependencies
You can declare twitter4j as a build-time dependency the
ivy.xml file of your rule project, so that the library is resolved and downloaded when you deploy the rule:
<ivy-module>
...
<dependencies defaultconf="default">
...
<dependency name="twitter4j-core" org="org.twitter4j" rev="4.0.4" />
</dependencies>
</ivy-module>
To make sure the library is packaged with your rule (to resolve runtime-dependencies) add this line to the
bnd.bnd file of your rule project:
...
Include-Resource:\
...,\
@lib/twitter4j-core.jar
...
Testing our rule
Did it work? Well, let’s test it by following these steps:
- Create a couple of users (A and B), each with a different Twitter profile.
- Add a user segment “Twitter Influencers” with your brand new rule and set a number of followers that is greater than the number of followers of B but lower than the number of followers of A.
- Go to the homepage and add a User Segment Content Display portlet.
- Edit the portlet configuration so that a certain image is displayed for users that belong to the “Twitter Influencers” user segment.
- Finally, log in with users A and B. User A will see the image, while user B won’t.
Now your company is ready to target Twitter influencers to increase its presence in social networks! How? We’ll you’ll have to wait for the next entry
.
Best practices and improvements
Regarding Twitter credentials, it’s not a good idea to have them declared as constants in your code even if tokens can be reverted and regenerated. In this series of blog entries we’ll show you a better approach based on
Consumer Manager, a new Liferay tool to manage application settings.
Try this at home!
Though this example was quite simple, you can take it as a basis for more complex rules. Just image the possibilities, given the amount of potential external sources for user classification. Try to build your own rules and if you feel proud of them, contribute the code to our repository and write an entry about them!