Part II: 7Cogs is Dead! Long Live 7Cogs!

In the first installment of this series, I showed several (hopefully useful) code snippets demonstrating the programmatic creation of various things in Liferay - pages, portlets, and structured web content, which can be found in the smoldering ashes of the 7Cogs sample data included in Liferay until version 6.1.
 
I am using these snippets in an app that auto-populates a blank Liferay Site with interesting eye candy people and content, useful for showing demos and running tests that require a lot of user-generated content (you can see examples of such content in the third welcome video created for the 6.1 launch).
 
In this blog post, we'll look some of the code that generates the fake users and content.  These snippets build on, but are not not directly copied from, 7Cogs. So quality is most likely decreasing :)  But most of the Liferay logic is derived from 7Cogs.
 

As a Reminder

  • This should all work in the latest Liferay 6.1 CE and EE releases.
  • Not a lot of clever coding. I'll post the full source to git as part of the final installment and invite you to fork and fix.
  • Not a lot of error checking here.  If it fails it doesn't end up blowing up, but also doesn't continue.  I leave it to your trusty development skills to fix this.
  • A lot of this is hard-coded or assumes the English language is being used.  You can undo this with basic development skills as well!
  • I don't go into excruciating detail about some of the Liferay APIs used here.  Javadoc is your friend.  Most of the time.

Get a random point in time

1
2
3
4
5
6
7
// generate a random calendar within the past year
public static Calendar getCal() {
    Calendar now = Calendar.getInstance();
    int rHours = (int) (Math.random() * 8640);
    now.add(Calendar.HOUR, 0 - rHours);
    return now;
}
 
This method is used when setting the creation date of user-generated content, to simulate that the content was created at some point in the past.  It makes time-based visualizations more interesting!
 

Create a Profile Avatar/Picture

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// A bunch of random gravatar IDs for use when creating new users
public static final String[] GRAVATAR_IDS = new String[]{
    "205e460b479e2e5b48aec07710c08d50", 
    "c63392ca320086522cf4d55cbf1d3808", 
    "4d346581a3340e32cf93703c9ce46bd4",
    // and so on
};

// fetch a random portrait from gravatar and return its bytes
public static byte[] getPortraitBytes() throws Exception {

    String id = SocialDriverConstants.GRAVATAR_IDS[(int) (Math.random() *
        (double) SocialDriverConstants.GRAVATAR_IDS.length)];
    String urlS = "http://2.gravatar.com/avatar/" + id + "?s=80&d=identicon";
    URL url = new URL(urlS);
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    InputStream in = url.openStream();
    byte[] buf = new byte[2048];
    int bytesRead;
    while ((bytesRead = in.read(buf)) != -1) {
        bos.write(buf, 0, bytesRead);
    }
    in.close();
    return bos.toByteArray();
}
 
Now the fun, fragility, and questionable coding begins!  The SocialDriverConstants class has a bunch of hard-coded user names, job titles, Gravatar picture IDs, industry keywords for Wikipedia searches, and such, used when generating the content.  This code fetches a random picture from Gravatar for use when creating new user profiles later on.
 

Generate Friend Requests

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// generate a friend request and optionally confirm it
public static void addSocialRequest(
    User user, User receiverUser, boolean confirm)
    throws Exception {

    SocialRequest socialRequest = SocialRequestLocalServiceUtil.addRequest(
        user.getUserId(), 0, User.class.getName(), user.getUserId(),
        1, StringPool.BLANK,
        receiverUser.getUserId());

    if (confirm) {
        SocialRequestLocalServiceUtil.updateRequest(
            socialRequest.getRequestId(),
            SocialRequestConstants.STATUS_CONFIRM, new ThemeDisplay());
    }
}
 
This code links users together through Social Relations (aka Friending).  It will optionally confirm the friend request.  This is used to randomly friend users to make them less lonely and make the social graph appear more interesting.
 

Creating and assign fake Addresses

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static void assignAddressTo(User user)
    throws SystemException, PortalException {
    String street = "123 Anystreet";
    String city = "Doesnt Matter";
    String zip = "12342";
    List<Country> countrys = CountryServiceUtil.getCountries();
    Country country = countrys.get((int) (Math.random() * (double)
        countrys.size()));
    double rnd = Math.random();
    if (rnd < .1) {
        country = CountryServiceUtil.getCountryByName("United States");
    } else if (rnd < .2) {
        country = CountryServiceUtil.getCountryByName("Japan");
    }
    List<Region> regions = RegionServiceUtil.getRegions(country
        .getCountryId());
    long regionId = 0;
    if (regions != null && !regions.isEmpty()) {
        regionId = regions.get((int) (Math.random() * (double) regions
            .size())).getRegionId();
    }

    AddressLocalServiceUtil.addAddress(user.getUserId(),
        Contact.class.getName(), user.getContactId(),
        street, "", "", city, zip, regionId, country.getCountryId(),
        11002, true, true);
}
 
Here we are randomly creating an Address and assigning it to a user, for later visualization.  You can also see a slight bias for the United States and Japan (you can change this).  10% of the time, the user's address will be in the US.  10% of the time, it will be in Japan.  The other 80% of the time it'll be in a random country in the world (of the countries defined by Liferay).  This is used later when visualizing user's locations.
 
You may be wondering what that hard-coded 11002 is doing there.  Addresses can be of different types (e.g. Personal, Business, and Other. 11002 is the constant for Personal.  This is defined in Liferay's default SQL data, and not in Java, so I had to hard-code it here.
 

Setting User Password, bypassing TOU, Father's Middle Name

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// make it easy to login the first time
private static void setFirstLogin(String password, User user)
    throws Exception {
    UserLocalServiceUtil.updatePortrait(user.getUserId(),
        getPortraitBytes());

    UserLocalServiceUtil.updateLastLogin(
        user.getUserId(), user.getLoginIP());
    UserLocalServiceUtil.updateAgreedToTermsOfUse(user.getUserId(), true);
    UserLocalServiceUtil.updatePassword(user.getUserId(),
        password.toLowerCase(), password.toLowerCase(),
        false, true);
    UserLocalServiceUtil.updatePasswordReset(user.getUserId(), false);


    String[] questions = StringUtil.split(
        PropsUtil.get("users.reminder.queries.questions"));

    String question = questions[0];
    String answer = "1234";

    UserLocalServiceUtil.updateReminderQuery(
        user.getUserId(), question, answer);
}
 
This makes it such that you can log in with the newly created user without having to set password, agree to the terms of use, or set your reminder questions.  The password is set to the user's first name, using all lower-case letters.  So John Smith's password will be john.
 
It also sets a user's profile picture using a random Gravatar ID through the call to getPortraitBytes defined earlier.
 

Randomly assigning some Friends

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// make a fake social network of friends by randomly friending some
// people
private static void assignRandomFriends(User user) throws Exception {
    int userCount = UserLocalServiceUtil.getUsersCount();
    int friendCount = (int) (Math.random() * (double) userCount);
    for (int i = 0; i < friendCount; i++) {
        int frienduser = (int) (Math.random() * (double) userCount);
        User randUser = UserLocalServiceUtil.getUsers(frienduser,
            frienduser + 1).get(0);
        if (randUser.getUserId() == user.getUserId()) continue;
        if (randUser.isDefaultUser()) continue;
        boolean confirm = (Math.random() > .4);
        addSocialRequest(user, randUser, confirm);
    }
}
 
This picks some random users with which a relationship is established.  It avoids friending the "default" ( guest) user, and also avoids friending the user with themselves (which is weird, right?).  It uses the previously defined addSocialRequest from up above.
 

Setting up a User's Profile

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// setup a user's public/private page theme, and construct some useful
// profile pages
private static void setupUserProfile(long companyId, String themeId,
                                     Group guestGroup, User user)
    throws Exception {
    Group group = user.getGroup();
    if (themeId != null && !themeId.isEmpty()) {
        LayoutSetLocalServiceUtil.updateLookAndFeel(
            group.getGroupId(), false, themeId, "01", "",
            false);
    }

    // Profile layout

    Layout layout = addLayout(
        group, user.getFullName(), false, "/profile", "2_columns_ii");
    JournalArticle cloudArticle, assetListArticle;

    try {
        cloudArticle = JournalArticleLocalServiceUtil.getLatestArticle
            (guestGroup.getGroupId(),
            SocialDriverConstants.CLOUD_ARTICLE_ID);
    } catch (NoSuchArticleException ex) {
        cloudArticle = addArticle("/cloud-structure.xml",
            "/cloud-template.vm", "cloud-article.xml",
            UserLocalServiceUtil.getDefaultUserId(companyId),
            guestGroup.getGroupId(),
            SocialDriverConstants.CLOUD_ARTICLE_ID);
    }

    try {
        assetListArticle = JournalArticleLocalServiceUtil
            .getLatestArticle(guestGroup.getGroupId(),
            SocialDriverConstants.ASSETLIST_ARTICLE_ID);
    } catch (NoSuchArticleException ex) {
        assetListArticle = addArticle("/assetlist-structure.xml",
            "/assetlist-template.vm", "assetlist-article.xml",
            UserLocalServiceUtil.getDefaultUserId(companyId),
            guestGroup.getGroupId(),
            SocialDriverConstants.ASSETLIST_ARTICLE_ID);
    }
    try {
        JournalArticleLocalServiceUtil.getLatestArticle(guestGroup
            .getGroupId(),
            SocialDriverConstants.EXPERTSLIST_ARTICLE_ID);
    } catch (NoSuchArticleException ex) {
        addArticle("/experts-structure.xml", "/experts-template.vm",
            "experts-article.xml",
            UserLocalServiceUtil.getDefaultUserId(companyId),
            guestGroup.getGroupId(),
            SocialDriverConstants.EXPERTSLIST_ARTICLE_ID);
    }

    addPortletId(layout, "1_WAR_socialnetworkingportlet", "column-1");
    addPortletId(layout, PortletKeys.REQUESTS, "column-1");
    String portletId = addPortletId(layout, PortletKeys.JOURNAL_CONTENT,
        "column-1");
    configureJournalContent(
        layout, guestGroup, portletId, cloudArticle.getArticleId());
    configurePortletTitle(layout, portletId, "Expertise");
    addPortletBorder(layout, portletId);

    addPortletBorder(layout, addPortletId(layout,
        "2_WAR_socialnetworkingportlet", "column-1"));

    addPortletBorder(layout, addPortletId(layout, PortletKeys.ACTIVITIES,
        "column-2"));
    addPortletBorder(layout, addPortletId(layout,
        "3_WAR_socialnetworkingportlet", "column-2"));

    // Expertise layout

    layout = addLayout(group, "Expertise", false, "/expertise",
        "2_columns_ii");

    addPortletId(layout, "1_WAR_socialnetworkingportlet", "column-1");
    addPortletId(layout, PortletKeys.REQUESTS, "column-1");
    portletId = addPortletId(layout, PortletKeys.JOURNAL_CONTENT,
        "column-1");
    configureJournalContent(
        layout, guestGroup, portletId, cloudArticle.getArticleId());
    configurePortletTitle(layout, portletId, "Expertise");
    addPortletBorder(layout, portletId);

    portletId = addPortletId(layout, PortletKeys.JOURNAL_CONTENT,
        "column-2");
    configureJournalContent(
        layout, guestGroup, portletId, assetListArticle.getArticleId());
    configurePortletTitle(layout, portletId, user.getFirstName() + "'s " +
        "Contributions");
    addPortletBorder(layout, portletId);

    // Social layout

    layout = addLayout(group, "Social", false, "/social", "2_columns_ii");

    addPortletId(layout, "1_WAR_socialnetworkingportlet", "column-1");
    addPortletId(layout, PortletKeys.REQUESTS, "column-1");
    addPortletBorder(layout, addPortletId(layout,
        "2_WAR_socialnetworkingportlet", "column-1"));
    addPortletBorder(layout, addPortletId(layout, PortletKeys.ACTIVITIES,
        "column-2"));
    addPortletBorder(layout, addPortletId(layout,
        "3_WAR_socialnetworkingportlet", "column-2"));

    // Blog layout

    layout = addLayout(group, "Blog", false, "/blog", "2_columns_ii");

    addPortletBorder(layout, addPortletId(layout,
        PortletKeys.RECENT_BLOGGERS, "column-1"));
    addPortletBorder(layout, addPortletId(layout, PortletKeys.BLOGS,
        "column-2"));

    // Workspace layout

    layout = addLayout(
        group, "Workspace", false, "/workspace", "2_columns_i");

    addPortletBorder(layout, addPortletId(layout,
        PortletKeys.RECENT_DOCUMENTS, "column-1"));
    addPortletBorder(layout, addPortletId(layout,
        PortletKeys.DOCUMENT_LIBRARY, "column-2"));

    addPortletId(layout, PortletKeys.CALENDAR, "column-2");
}
 
This rather long method is in the spirit of 7Cogs.  It first assigns the user's public pages a theme (lines 7-10) (if this option is chosen in the GUI to be seen later).  It then adds three Web Content Articles (lines 19-52) (each based on a Web Content Template and Structure).  These are special web content articles that I'll show you in the next installment in this series.  These articles, along with some other Liferay portlets (like a calendar, documents and media, recent bloggers, etc), are placed on the user's newly-created 4 profile pages (lines 54-126):
 
  • User's Name (Friendly URL: /profile)
  • Expertise (Friendly URL: /expertise)
  • Social (Friendly URL: /social)
  • Blog (Friendly URL: /blog)
  • Workspace (Friendly URL: /workspace)
 

Programmatically Creating a Liferay User

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// add a new user to the DB, using random fake data,
// and set up their profile pages, and add
// them to the right sites, friend a couple of other random users.
public synchronized static User addUser(String firstName, long companyId,
                                        String themeId, boolean profileFlag)
    throws Exception {

    String lastName = getRndStr(SocialDriverConstants.LAST_NAMES);
    String job = getRndStr(SocialDriverConstants.JOB_TITLES);
    Group guestGroup = GroupLocalServiceUtil.getGroup(
        companyId, GroupConstants.GUEST);

    long[] groupIds;
    try {
        // see if there's a demo sales group, and add the user to the
        // group if so
        Group salesGroup = GroupLocalServiceUtil.getGroup(
            companyId, "Sales");
        groupIds = new long[]{guestGroup.getGroupId(),
            salesGroup.getGroupId()};
    } catch (Exception ex) {
        groupIds = new long[]{guestGroup.getGroupId()};
    }
    ServiceContext serviceContext = new ServiceContext();

    long[] roleIds;
    try {
        // see if we're using social office, and add SO's role to the new
        // user if so
        roleIds = new long[]{RoleLocalServiceUtil.getRole(companyId,
            "Social Office User").getRoleId()};
        serviceContext.setScopeGroupId(guestGroup.getGroupId());
    } catch (Exception ignored) {
        roleIds = new long[]{};
    }
    User user = UserLocalServiceUtil.addUser(UserLocalServiceUtil
        .getDefaultUserId(companyId), companyId, false,
        firstName,
        firstName,
        false, firstName.toLowerCase(), firstName.toLowerCase() +
        "@liferay.com", 0, "",
        LocaleUtil.getDefault(), firstName,
        "", lastName, 0, 0, true, 8, 15, 1974, job, groupIds,
        new long[]{}, roleIds, new long[]{},
        false, serviceContext);

    assignAddressTo(user);
    setFirstLogin(firstName, user);
    assignRandomFriends(user);


    if (profileFlag) {
        setupUserProfile(companyId, themeId, guestGroup, user);
    }
    return user;

}
 
This code creates a new Liferay User.  Some notes:
  • If it finds a Site called "Sales", it'll assign the newly created user to it, in addition to assigning them to the Guest Site.  This is because I use a demo group called "Sales" (mimics a typical enterprise sales organization) so I want to add users to it if such a site exists.
  • It will also imbue the new user with the "Social Office User" role if such a role exists (and it will, if you are using Social Office).
  • Note that Social Office comes with a nice looking theme, so in most cases if you are using SO, you'll not want the theme assignment to happen.  There is an option in the UI for this.
  • It finally assigns a random address to the user, configures their account to make it easy to login the first time, assigns random friends, and then sets up the new user's profile as defined above.
 

Get (or create) a random User

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// retrieves a random user from the company.  If the randomly chosen user
// doesn't exist, create them first
public static long getUserId(long companyId, String themeId,
                             boolean profileFlag)
    throws Exception {

    User user;
    String first = getRndStr(SocialDriverConstants.FIRST_NAMES);
    try {
        user = UserLocalServiceUtil.getUserByScreenName(companyId,
            first.toLowerCase());
    } catch (NoSuchUserException ex) {
        user = addUser(first, companyId, themeId, profileFlag);
    }
    return user.getUserId();

}
 
This code will pick a random user from the set of defined users in our constants file, and if they don't exist yet, it'll create them (via prior code above).  It is used when creating fake content (blogs, wikis, ratings, comments, etc).
 

Removing Stop Words

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// standard list of common english words to eliminate from tags
public static final List<String> ENGLISH_STOP_WORDS = Arrays.asList(
    "a", "an", "and", "are", "as", "at", "be", "but", "by",
    "for", "if", "in", "into", "is", "it",
    "no", "not", "of", "on", "or", "such",
    "that", "the", "their", "then", "there", "these",
    "they", "this", "to", "was", "will", "with");


// remove common tags from the array of passed-in tags
public static String[] removeEnglishStopWords(String[] a) {
    List<String> result = new ArrayList<String>();
    for (String as : a) {
        if (SocialDriverConstants.ENGLISH_STOP_WORDS.contains(as)) continue;
        if (as.length() < 3) continue;
        result.add(as);
    }

    return result.toArray(new String[result.size()]);
}
 
This removes common English Stop Words, those that are very common and are useless as tags, such as The or A, or any word that is 2 characters or less, which are pretty useless as tags too.  I'm pretty sure the Big-O performance of this is really bad.
 

User-Generated Content

As part of this app, we also generate a bunch of user content in the form of:
  • Blog Posts
  • Wiki Articles
  • Forum Posts
  • Blog, Wiki comments
  • Forum responses
  • Blog, Wiki, Forum ratings/votes
The content is generated by going out to Wikipedia and fetching articles based on pre-defined search terms.  The title of each article is used to generate a set of tags for the article (ignoring common words like a and the).  The Wikipedia JSON service is used and abused.  This content is cached for use while the generator is running.  Here's how to fetch it:
 

Picking terms for Wikipedia Searches

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// hard-coded set of tags to use when searching wikipedia
public static final String[] FAVORED_TAGS = new String[]{
    "java",
    "liferay",
    "javaee",
    "ldap",
    "portal",
    "content management system",
    "social networking",
    "mobile",
    "android",
    "cloud computing",
    "j2ee",
    "enterprise software",
    "sharepoint",
    "single sign on",
    "gadgets",
    "open source",
    "collaboration"
};


public static String getRndStr(String[] ar) {
    return ar[(int) (Math.random() * (double) ar.length)];
}

// get some random terms to search for, with no duplicate terms
private String[] getRandomTerms(String[] allTerms) {

    int termCount = 1 + (int) (Math.random() *
        SocialDriverConstants.MAX_SEARCH_TERMS);
    List<String> result = new ArrayList<String>();
    String term;
    for (int j = 0; j < termCount; j++) {
        while (result.contains(term =
            SocialDriverUtil.getRndStr(allTerms))) {
            // do nothing
        }
        result.add(term);
    }
    return (String[]) result.toArray(new String[result.size()]);
}
 
Here we are just picking a random number of random terms from a pre-defined list.  Woo!  Note if you pick too many terms, you won't find anything on Wikipedia (since it's an AND search).  And if you pick too few, you'll get a LOT (which is great, but sometimes searching for java might get you coffee articles, or searching for mobile will get you information about a certain southern US city.  So it's good to have a set of related keywords.
 

Fetching articles from wikipedia

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private static Set<ContentArticle> fetchWikipediaArticles(HttpClient
                                                              httpclient,
                                                          String[] terms)
    throws IOException, JSONException {
    int hitLength;
    Set<ContentArticle> result = new HashSet<ContentArticle>();

    StringBuilder sb = new StringBuilder();
    sb.append("http://en.wikipedia.org/w/api" +
        ".php?action=query&list=search&format=json&srsearch=");
    sb.append(HtmlUtil.escapeURL(StringUtil.merge(terms, ",")));
    sb.append("&srnamespace=0&srwhat=text&srprop=snippet&srlimit=");
    sb.append(SocialDriverConstants.WIKIPEDIA_MAX_ARTICLE_COUNT);

    GetMethod get = new GetMethod(sb.toString());
    httpclient.executeMethod(get);
    JSONObject wikis = JSONFactoryUtil.createJSONObject(get
        .getResponseBodyAsString());
    JSONArray qresult = wikis.getJSONObject("query").getJSONArray("search");
    hitLength = qresult.length();

    for (int k = 0; k < hitLength; k++) {
        JSONObject hit = qresult.getJSONObject(k);
        String title = hit.getString("title");
        String excerpt = hit.getString("snippet");
        if (excerpt == null || excerpt.isEmpty()) continue;
        String[] words = SocialDriverUtil.removeEnglishStopWords(title
            .replaceAll("[^A-Za-z0-9]", " ").split("\\s+"));
        result.add(new ContentArticle(title, excerpt, words));
    }
    return result;
}
 
Not much to do with Liferay, but this method queries Wikipedia via its JSON web services, using the supplied String, and established HTTPClient object (this uses Liferay's built-in, old version of the Commons HttpClient).
 
The result of a JSON web service call to Wikipedia is a JSON object (see their docs) containing the results of the search.   You can see what it looks like in your browser.  We grab the title, and break it apart (and remove stop words) and use the result as the tags for the article.  The body is represented by the article's  snippet (you can't get the full content from Wikipedia due to usage restrictions).  The result of calling this method is a library of ContentArticle ojects that are very simple (just a container for the title, tags, and content of an article, used later on to make the content in Liferay).  You'll see how this all comes together in the next blog post in this series.
 
By the way, you can repeatedly call this to get more articles as needed.  It's a good idea to call it a several times (like 10 times with different search terms if you want to generate a LOT of Liferay content).
 

Creating a Blog Post

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void addEntry() throws Exception {
    Calendar rCal = SocialDriverUtil.getCal();


    ServiceContext context = new ServiceContext();
    context.setCreateDate(rCal.getTime());
    context.setModifiedDate(rCal.getTime());
    String cid = contentContainer.getRandomId();
    String title = contentContainer.getContentTitle(cid);
    String content = contentContainer.getContentBody(cid);
    String[] tags = contentContainer.getContentTags(cid);
    context.setWorkflowAction(WorkflowConstants.ACTION_PUBLISH);
    context.setAddGroupPermissions(true);
    context.setAddGuestPermissions(true);

    context.setAssetTagNames(tags);
    context.setCompanyId(companyId);
    context.setScopeGroupId(groupId);

    BlogsEntry newEntry = BlogsEntryLocalServiceUtil.addEntry
        (SocialDriverUtil.getUserId(companyId, themeId, profileFlag),
            title, "",
        content,
        rCal.get(Calendar.MONTH), rCal.get(Calendar.DAY_OF_MONTH),
        rCal.get(Calendar.YEAR),
        rCal.get(Calendar.HOUR_OF_DAY), rCal.get(Calendar.MINUTE), false,
            false, null, false, null,
        null, null, context);

    System.out.println("Added blog " + newEntry.getTitle() + " Tags: " +
        Arrays.asList(tags));
}
 
Now we are back to Liferay APIs.  Here we are randomly picking a date and a user and publishing a blog post using random content and tags from earlier Wikipedia fetches.  This has the side effect of making a new Activity Stream entry, and accumulating Social Activity points!  More on this in the next installment.  We are doing the ServiceBuilder dance here as well, just like before.
 

Commenting on a Blog Post

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private void commentEntry() throws Exception {

    if (BlogsEntryLocalServiceUtil.getBlogsEntriesCount() <= 0) return;
    int rand = (int) (Math.random() * (double) BlogsEntryLocalServiceUtil
        .getBlogsEntriesCount());
    BlogsEntry entry = BlogsEntryLocalServiceUtil.getBlogsEntries(rand,
        rand + 1).get(0);
    Calendar rCal = SocialDriverUtil.getCal();

    long userId = SocialDriverUtil.getUserId(companyId, themeId,
        profileFlag);
    ServiceContext context = new ServiceContext();
    context.setCreateDate(rCal.getTime());
    context.setModifiedDate(rCal.getTime());
    context.setCompanyId(companyId);
    context.setWorkflowAction(WorkflowConstants.ACTION_PUBLISH);
    context.setAddGroupPermissions(true);
    context.setAddGuestPermissions(true);

    context.setScopeGroupId(groupId);

    MBDiscussion disc = MBDiscussionLocalServiceUtil.getDiscussion
        (BlogsEntry.class.getName(),
        entry.getPrimaryKey());
    MBMessageLocalServiceUtil.addDiscussionMessage(userId, "Joe Schmoe",
        context.getScopeGroupId(), BlogsEntry.class.getName(),
        entry.getPrimaryKey(),
        disc.getThreadId(), MBThreadLocalServiceUtil.getMBThread(disc
        .getThreadId()).getRootMessageId(),
        "Subject of comment", "This is great", context);

    System.out.println("Commented on Blog " + entry.getTitle());
}
 
This is where things get even more interesting.  Comments in Liferay are created and managed through the MB (Message Board) functionality.  So, you'll see some of those APIs here.  Discussions are associated with content (like blog posts) through the standard className/classPK linkage pattern found throughout Liferay's Asset Framework.  So this code simply picks a random, existing blog post to comment on, and adds the comment to the thread associated with the blog post.  Other content types are very similar (e.g. Wikis, Documents, etc).
 

Voting on a Blog Post

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void voteEntry() throws Exception {

    if (BlogsEntryLocalServiceUtil.getBlogsEntriesCount() <= 0) return;
    int rand = (int) (Math.random() * (double) BlogsEntryLocalServiceUtil.getBlogsEntriesCount());
    BlogsEntry entry = BlogsEntryLocalServiceUtil.getBlogsEntries(rand, rand + 1).get(0);
    Calendar rCal = SocialDriverUtil.getCal();

    long userId = SocialDriverUtil.getUserId(companyId, themeId, profileFlag);
    ServiceContext context = new ServiceContext();
    context.setCreateDate(rCal.getTime());
    context.setModifiedDate(rCal.getTime());
    context.setCompanyId(companyId);
    context.setWorkflowAction(WorkflowConstants.ACTION_PUBLISH);
    context.setAddGroupPermissions(true);
    context.setAddGuestPermissions(true);

    context.setScopeGroupId(groupId);

    PrincipalThreadLocal.setName(userId);
    RatingsEntryServiceUtil.updateEntry(BlogsEntry.class.getName(), entry.getEntryId(),
        (int) (Math.random() * 5.0) + 1);
    System.out.println("Voted on Blog " + entry.getTitle());
}
 
Here is how to programmatically find and randomly rate  a random blog post (1 to 5 stars), using the RatingsEntry service.  The deal with the PrincipalThreadLocal on line 19 is that this is how Liferay knows "who" is voting (since voting is typically done in the context of a request/response).
 

Creating, Commenting, and Rating other Content Types

Looks at watch - Wow, where did the time go?  This will have to wait for the next installment, and I will show you how to wrap all of this into a simple Liferay portlet with a horrible yet usable UI for controlling the creation of content and social data.
Blogs