001    /**
002     * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portlet.social.service.impl;
016    
017    import com.liferay.portal.kernel.cache.MultiVMPoolUtil;
018    import com.liferay.portal.kernel.cache.PortalCache;
019    import com.liferay.portal.kernel.dao.db.DB;
020    import com.liferay.portal.kernel.dao.db.DBFactoryUtil;
021    import com.liferay.portal.kernel.dao.orm.QueryUtil;
022    import com.liferay.portal.kernel.exception.PortalException;
023    import com.liferay.portal.kernel.exception.SystemException;
024    import com.liferay.portal.kernel.log.Log;
025    import com.liferay.portal.kernel.log.LogFactoryUtil;
026    import com.liferay.portal.kernel.transaction.Propagation;
027    import com.liferay.portal.kernel.transaction.Transactional;
028    import com.liferay.portal.kernel.util.PropsKeys;
029    import com.liferay.portal.kernel.util.StringBundler;
030    import com.liferay.portal.kernel.util.StringPool;
031    import com.liferay.portal.kernel.util.StringUtil;
032    import com.liferay.portal.kernel.util.Tuple;
033    import com.liferay.portal.model.Group;
034    import com.liferay.portal.model.Lock;
035    import com.liferay.portal.model.User;
036    import com.liferay.portal.util.PortalUtil;
037    import com.liferay.portal.util.PropsValues;
038    import com.liferay.portlet.asset.model.AssetEntry;
039    import com.liferay.portlet.social.model.SocialAchievement;
040    import com.liferay.portlet.social.model.SocialActivity;
041    import com.liferay.portlet.social.model.SocialActivityCounter;
042    import com.liferay.portlet.social.model.SocialActivityCounterConstants;
043    import com.liferay.portlet.social.model.SocialActivityCounterDefinition;
044    import com.liferay.portlet.social.model.SocialActivityDefinition;
045    import com.liferay.portlet.social.model.SocialActivityLimit;
046    import com.liferay.portlet.social.model.SocialActivityProcessor;
047    import com.liferay.portlet.social.service.SocialActivityCounterLocalService;
048    import com.liferay.portlet.social.service.base.SocialActivityCounterLocalServiceBaseImpl;
049    import com.liferay.portlet.social.service.persistence.SocialActivityCounterFinder;
050    import com.liferay.portlet.social.util.SocialCounterPeriodUtil;
051    
052    import java.util.Arrays;
053    import java.util.Collections;
054    import java.util.Date;
055    import java.util.HashMap;
056    import java.util.List;
057    import java.util.Map;
058    
059    /**
060     * The social activity counter local service. This service is responsible for
061     * creating and/or incrementing counters in response to an activity. It also
062     * provides methods for querying activity counters within a time period.
063     *
064     * <p>
065     * Under normal circumstances only the {@link
066     * #addActivityCounters(SocialActivity)} should be called directly and even that
067     * is usually not necessary as it is automatically called by the social activity
068     * service.
069     * </p>
070     *
071     * @author Zsolt Berentey
072     * @author Shuyang Zhou
073     */
074    public class SocialActivityCounterLocalServiceImpl
075            extends SocialActivityCounterLocalServiceBaseImpl {
076    
077            /**
078             * Adds an activity counter with a default period length.
079             *
080             * <p>
081             * This method uses the lock service to guard against multiple threads
082             * trying to insert the same counter because this service is called
083             * asynchronously from the social activity service.
084             * </p>
085             *
086             * @param  groupId the primary key of the group
087             * @param  classNameId the primary key of the entity's class this counter
088             *         belongs to
089             * @param  classPK the primary key of the entity this counter belongs to
090             * @param  name the counter's name
091             * @param  ownerType the counter's owner type. Acceptable values are
092             *         <code>TYPE_ACTOR</code>, <code>TYPE_ASSET</code> and
093             *         <code>TYPE_CREATOR</code> defined in {@link
094             *         com.liferay.portlet.social.model.SocialActivityCounterConstants}.
095             * @param  currentValue the counter's current value (optionally
096             *         <code>0</code>)
097             * @param  totalValue the counter's total value (optionally <code>0</code>)
098             * @param  startPeriod the counter's start period
099             * @param  endPeriod the counter's end period
100             * @return the added activity counter
101             * @throws PortalException if the group or the previous activity counter
102             *         could not be found
103             * @throws SystemException if a system exception occurred
104             */
105            public SocialActivityCounter addActivityCounter(
106                            long groupId, long classNameId, long classPK, String name,
107                            int ownerType, int currentValue, int totalValue, int startPeriod,
108                            int endPeriod)
109                    throws PortalException, SystemException {
110    
111                    return addActivityCounter(
112                            groupId, classNameId, classPK, name, ownerType, currentValue,
113                            totalValue, startPeriod, endPeriod, 0, 0);
114            }
115    
116            /**
117             * Adds an activity counter specifying a previous activity and period
118             * length.
119             *
120             * <p>
121             * This method uses the lock service to guard against multiple threads
122             * trying to insert the same counter because this service is called
123             * asynchronously from the social activity service.
124             * </p>
125             *
126             * @param  groupId the primary key of the group
127             * @param  classNameId the primary key of the entity's class this counter
128             *         belongs to
129             * @param  classPK the primary key of the entity this counter belongs to
130             * @param  name the counter name
131             * @param  ownerType the counter's owner type. Acceptable values are
132             *         <code>TYPE_ACTOR</code>, <code>TYPE_ASSET</code> and
133             *         <code>TYPE_CREATOR</code> defined in {@link
134             *         com.liferay.portlet.social.model.SocialActivityCounterConstants}.
135             * @param  currentValue the current value of the counter (optionally
136             *         <code>0</code>)
137             * @param  totalValue the counter's total value (optionally <code>0</code>)
138             * @param  startPeriod the counter's start period
139             * @param  endPeriod the counter's end period
140             * @param  previousActivityCounterId the primary key of the activity counter
141             *         for the previous time period (optionally <code>0</code>, if this
142             *         is the first)
143             * @param  periodLength the period length in days,
144             *         <code>PERIOD_LENGTH_INFINITE</code> for never ending counters or
145             *         <code>PERIOD_LENGTH_SYSTEM</code> for the period length defined
146             *         in <code>portal-ext.properties</code>. For more information see
147             *         {@link
148             *         com.liferay.portlet.social.model.SocialActivityCounterConstants}.
149             * @return the added activity counter
150             * @throws PortalException if the group or the previous activity counter
151             *         could not be found
152             * @throws SystemException if a system exception occurred
153             */
154            public SocialActivityCounter addActivityCounter(
155                            long groupId, long classNameId, long classPK, String name,
156                            int ownerType, int currentValue, int totalValue, int startPeriod,
157                            int endPeriod, long previousActivityCounterId, int periodLength)
158                    throws PortalException, SystemException {
159    
160                    SocialActivityCounter activityCounter = null;
161    
162                    String lockKey = getLockKey(
163                            groupId, classNameId, classPK, name, ownerType);
164    
165                    Lock lock = null;
166    
167                    while (true) {
168                            try {
169                                    lock = lockLocalService.lock(
170                                            SocialActivityCounter.class.getName(), lockKey, lockKey,
171                                            false);
172                            }
173                            catch (Exception e) {
174                                    if (_log.isWarnEnabled()) {
175                                            _log.warn(
176                                                    "Unable to acquire activity counter lock. Retrying.");
177                                    }
178    
179                                    continue;
180                            }
181    
182                            if (lock.isNew()) {
183                                    try {
184                                            DB db = DBFactoryUtil.getDB();
185    
186                                            String dbType = db.getType();
187    
188                                            if (dbType.equals(DB.TYPE_HYPERSONIC)) {
189    
190                                                    // LPS-25408
191    
192                                                    activityCounter = createActivityCounter(
193                                                            groupId, classNameId, classPK, name, ownerType,
194                                                            currentValue, totalValue, startPeriod, endPeriod,
195                                                            previousActivityCounterId, periodLength);
196                                            }
197                                            else {
198                                                    activityCounter =
199                                                            socialActivityCounterLocalService.
200                                                                    createActivityCounter(
201                                                                            groupId, classNameId, classPK, name,
202                                                                            ownerType, currentValue, totalValue,
203                                                                            startPeriod, endPeriod,
204                                                                            previousActivityCounterId, periodLength);
205    
206                                            }
207                                    }
208                                    finally {
209                                            lockLocalService.unlock(
210                                                    SocialActivityCounter.class.getName(), lockKey, lockKey,
211                                                    false);
212                                    }
213    
214                                    break;
215                            }
216    
217                            Date createDate = lock.getCreateDate();
218    
219                            if ((System.currentTimeMillis() - createDate.getTime()) >=
220                                            PropsValues.SOCIAL_ACTIVITY_COUNTER_LOCK_TIMEOUT) {
221    
222                                    lockLocalService.unlock(
223                                            SocialActivityCounter.class.getName(), lockKey,
224                                            lock.getOwner(), false);
225    
226                                    if (_log.isWarnEnabled()) {
227                                            _log.warn(
228                                                    "Forcibly removed lock " + lock + ". See " +
229                                                            PropsKeys.SOCIAL_ACTIVITY_COUNTER_LOCK_TIMEOUT);
230                                    }
231                            }
232                            else {
233                                    try {
234                                            Thread.sleep(
235                                                    PropsValues.SOCIAL_ACTIVITY_COUNTER_LOCK_RETRY_DELAY);
236                                    }
237                                    catch (InterruptedException ie) {
238                                            if (_log.isWarnEnabled()) {
239                                                    _log.warn(
240                                                            "Interrupted while waiting to reacquire lock", ie);
241                                            }
242                                    }
243                            }
244                    }
245    
246                    return activityCounter;
247            }
248    
249            /**
250             * Adds or increments activity counters related to an activity.
251             *
252             * </p>
253             * This method is called asynchronously from the social activity service
254             * when the user performs an activity defined in
255             * </code>liferay-social.xml</code>.
256             * </p>
257             *
258             * <p>
259             * This method first calls the activity processor class, if there is one
260             * defined for the activity, checks for limits and increments all the
261             * counters that belong to the activity. Afterwards, it processes the
262             * activity with respect to achievement classes, if any. Lastly it
263             * increments the built-in <code>user.activities</code> and
264             * <code>asset.activities</code> counters.
265             * </p>
266             *
267             * @param  activity the social activity
268             * @throws PortalException if an expected group or expected previous
269             *         activity counters could not be found
270             * @throws SystemException if a system exception occurred
271             */
272            public void addActivityCounters(SocialActivity activity)
273                    throws PortalException, SystemException {
274    
275                    if (!socialActivitySettingLocalService.isEnabled(
276                                    activity.getGroupId(), activity.getClassNameId())) {
277    
278                            return;
279                    }
280    
281                    if (!socialActivitySettingLocalService.isEnabled(
282                                    activity.getGroupId(), activity.getClassNameId(),
283                                    activity.getClassPK())) {
284    
285                            return;
286                    }
287    
288                    User user = userPersistence.findByPrimaryKey(activity.getUserId());
289    
290                    SocialActivityDefinition activityDefinition =
291                            socialActivitySettingLocalService.getActivityDefinition(
292                                    activity.getGroupId(), activity.getClassName(),
293                                    activity.getType());
294    
295                    if ((activityDefinition == null) || !activityDefinition.isEnabled()) {
296                            return;
297                    }
298    
299                    SocialActivityProcessor activityProcessor =
300                            activityDefinition.getActivityProcessor();
301    
302                    if (activityProcessor != null) {
303                            activityProcessor.processActivity(activity);
304                    }
305    
306                    AssetEntry assetEntry = activity.getAssetEntry();
307    
308                    User assetEntryUser = userPersistence.findByPrimaryKey(
309                            assetEntry.getUserId());
310    
311                    for (SocialActivityCounterDefinition activityCounterDefinition :
312                                    activityDefinition.getActivityCounterDefinitions()) {
313    
314                            if (addActivityCounter(
315                                            user, assetEntryUser, activityCounterDefinition) &&
316                                    checkActivityLimit(user, activity, activityCounterDefinition)) {
317    
318                                    incrementActivityCounter(
319                                            activity.getGroupId(), user, activity.getAssetEntry(),
320                                            activityCounterDefinition);
321                            }
322                    }
323    
324                    for (SocialAchievement achievement :
325                                    activityDefinition.getAchievements()) {
326    
327                            achievement.processActivity(activity);
328                    }
329    
330                    if (!user.isDefaultUser() && user.isActive()) {
331                            incrementActivityCounter(
332                                    activity.getGroupId(),
333                                    PortalUtil.getClassNameId(User.class.getName()),
334                                    activity.getUserId(),
335                                    SocialActivityCounterConstants.NAME_USER_ACTIVITIES,
336                                    SocialActivityCounterConstants.TYPE_ACTOR, 1,
337                                    SocialActivityCounterConstants.PERIOD_LENGTH_SYSTEM);
338                    }
339    
340                    if (!assetEntryUser.isDefaultUser() && assetEntryUser.isActive()) {
341                            incrementActivityCounter(
342                                    activity.getGroupId(), activity.getClassNameId(),
343                                    activity.getClassPK(),
344                                    SocialActivityCounterConstants.NAME_ASSET_ACTIVITIES,
345                                    SocialActivityCounterConstants.TYPE_ASSET, 1,
346                                    SocialActivityCounterConstants.PERIOD_LENGTH_SYSTEM);
347                    }
348            }
349    
350            /**
351             * Creates an activity counter with a default period length, adding it into
352             * the database.
353             *
354             * @param      groupId the primary key of the group
355             * @param      classNameId the primary key of the entity's class this
356             *             counter belongs to
357             * @param      classPK the primary key of the entity this counter belongs to
358             * @param      name the counter's name
359             * @param      ownerType the counter's owner type. Acceptable values are
360             *             <code>TYPE_ACTOR</code>, <code>TYPE_ASSET</code> and
361             *             <code>TYPE_CREATOR</code> defined in {@link
362             *             com.liferay.portlet.social.model.SocialActivityCounterConstants}.
363             * @param      currentValue the counter's current value (optionally
364             *             <code>0</code>)
365             * @param      totalValue the counter's total value (optionally
366             *             <code>0</code>)
367             * @param      startPeriod the counter's start period
368             * @param      endPeriod the counter's end period
369             * @return     the created activity counter
370             * @throws     PortalException if the group or a previous activity counter
371             *             could not be found
372             * @throws     SystemException if a system exception occurred
373             * @deprecated {@link #createActivityCounter(long, long, long, String, int,
374             *             int, int, int, int, long, int)}
375             */
376            @Transactional(propagation = Propagation.REQUIRES_NEW)
377            public SocialActivityCounter createActivityCounter(
378                            long groupId, long classNameId, long classPK, String name,
379                            int ownerType, int currentValue, int totalValue, int startPeriod,
380                            int endPeriod)
381                    throws PortalException, SystemException {
382    
383                    return createActivityCounter(
384                            groupId, classNameId, classPK, name, ownerType, currentValue,
385                            totalValue, startPeriod, endPeriod, 0, 0);
386            }
387    
388            /**
389             * Creates an activity counter, adding it into the database.
390             *
391             * <p>
392             * This method actually creates the counter in the database. It requires a
393             * new transaction so that other threads can find the new counter when the
394             * lock in the calling method is released.
395             * </p>
396             *
397             * @param  groupId the primary key of the group
398             * @param  classNameId the primary key of the entity's class this counter
399             *         belongs to
400             * @param  classPK the primary key of the entity this counter belongs to
401             * @param  name the counter's name
402             * @param  ownerType the counter's owner type. Acceptable values are
403             *         <code>TYPE_ACTOR</code>, <code>TYPE_ASSET</code> and
404             *         <code>TYPE_CREATOR</code> defined in {@link
405             *         com.liferay.portlet.social.model.SocialActivityCounterConstants}.
406             * @param  currentValue the counter's current value (optionally
407             *         <code>0</code>)
408             * @param  totalValue the counter's total value of the counter (optionally
409             *         <code>0</code>)
410             * @param  startPeriod the counter's start period
411             * @param  endPeriod the counter's end period
412             * @param  previousActivityCounterId the primary key of the activity counter
413             *         for the previous time period (optionally <code>0</code>, if this
414             *         is the first)
415             * @param  periodLength the period length in days,
416             *         <code>PERIOD_LENGTH_INFINITE</code> for never ending counters or
417             *         <code>PERIOD_LENGTH_SYSTEM</code> for the period length defined
418             *         in <code>portal-ext.properties</code>. For more information see
419             *         {@link com.liferay.portlet.social.model.SocialActivityConstants}.
420             * @return the created activity counter
421             * @throws PortalException if the group or the previous activity counter
422             *         could not be found
423             * @throws SystemException if a system exception occurred
424             */
425            @Transactional(propagation = Propagation.REQUIRES_NEW)
426            public SocialActivityCounter createActivityCounter(
427                            long groupId, long classNameId, long classPK, String name,
428                            int ownerType, int currentValue, int totalValue, int startPeriod,
429                            int endPeriod, long previousActivityCounterId, int periodLength)
430                    throws PortalException, SystemException {
431    
432                    SocialActivityCounter activityCounter = null;
433    
434                    if (previousActivityCounterId != 0) {
435                            activityCounter = socialActivityCounterPersistence.findByPrimaryKey(
436                                    previousActivityCounterId);
437    
438                            if (periodLength ==
439                                            SocialActivityCounterConstants.PERIOD_LENGTH_SYSTEM) {
440    
441                                    activityCounter.setEndPeriod(
442                                            SocialCounterPeriodUtil.getStartPeriod() - 1);
443                            }
444                            else {
445                                    activityCounter.setEndPeriod(
446                                            activityCounter.getStartPeriod() + periodLength - 1);
447                            }
448    
449                            socialActivityCounterPersistence.update(activityCounter);
450                    }
451    
452                    activityCounter = socialActivityCounterPersistence.fetchByG_C_C_N_O_E(
453                            groupId, classNameId, classPK, name, ownerType, endPeriod, false);
454    
455                    if (activityCounter != null) {
456                            return activityCounter;
457                    }
458    
459                    Group group = groupPersistence.findByPrimaryKey(groupId);
460    
461                    long activityCounterId = counterLocalService.increment();
462    
463                    activityCounter = socialActivityCounterPersistence.create(
464                            activityCounterId);
465    
466                    activityCounter.setGroupId(groupId);
467                    activityCounter.setCompanyId(group.getCompanyId());
468                    activityCounter.setClassNameId(classNameId);
469                    activityCounter.setClassPK(classPK);
470                    activityCounter.setName(name);
471                    activityCounter.setOwnerType(ownerType);
472                    activityCounter.setCurrentValue(currentValue);
473                    activityCounter.setTotalValue(totalValue);
474                    activityCounter.setStartPeriod(startPeriod);
475                    activityCounter.setEndPeriod(endPeriod);
476                    activityCounter.setActive(true);
477    
478                    socialActivityCounterPersistence.update(activityCounter);
479    
480                    return activityCounter;
481            }
482    
483            /**
484             * Deletes all activity counters, limits, and settings related to the asset.
485             *
486             * <p>
487             * This method subtracts the asset's popularity from the owner's
488             * contribution points. It also creates a new contribution period if the
489             * latest one does not belong to the current period.
490             * </p>
491             *
492             * @param  assetEntry the asset entry
493             * @throws PortalException if the new contribution counter could not be
494             *         created
495             * @throws SystemException if a system exception occurred
496             */
497            public void deleteActivityCounters(AssetEntry assetEntry)
498                    throws PortalException, SystemException {
499    
500                    if (assetEntry == null) {
501                            return;
502                    }
503    
504                    adjustUserContribution(assetEntry, false);
505    
506                    socialActivityCounterPersistence.removeByC_C(
507                            assetEntry.getClassNameId(), assetEntry.getClassPK());
508    
509                    socialActivityLimitPersistence.removeByC_C(
510                            assetEntry.getClassNameId(), assetEntry.getClassPK());
511    
512                    socialActivitySettingLocalService.deleteActivitySetting(
513                            assetEntry.getGroupId(), assetEntry.getClassName(),
514                            assetEntry.getClassPK());
515    
516                    clearFinderCache();
517            }
518    
519            /**
520             * Deletes all activity counters, limits, and settings related to the entity
521             * identified by the class name ID and class primary key.
522             *
523             * @param  classNameId the primary key of the entity's class
524             * @param  classPK the primary key of the entity
525             * @throws PortalException if the entity is an asset and its owner's
526             *         contribution counter could not be updated
527             * @throws SystemException if a system exception occurred
528             */
529            public void deleteActivityCounters(long classNameId, long classPK)
530                    throws PortalException, SystemException {
531    
532                    String className = PortalUtil.getClassName(classNameId);
533    
534                    if (!className.equals(User.class.getName())) {
535                            AssetEntry assetEntry = assetEntryLocalService.fetchEntry(
536                                    className, classPK);
537    
538                            deleteActivityCounters(assetEntry);
539                    }
540                    else {
541                            socialActivityCounterPersistence.removeByC_C(classNameId, classPK);
542    
543                            socialActivityLimitPersistence.removeByUserId(classPK);
544                    }
545    
546                    clearFinderCache();
547            }
548    
549            /**
550             * Deletes all activity counters for the entity identified by the class name
551             * and class primary key.
552             *
553             * @param  className the entity's class name
554             * @param  classPK the primary key of the entity
555             * @throws PortalException if the entity is an asset and its owner's
556             *         contribution counter could not be updated
557             * @throws SystemException if a system exception occurred
558             */
559            public void deleteActivityCounters(String className, long classPK)
560                    throws PortalException, SystemException {
561    
562                    if (!className.equals(User.class.getName())) {
563                            AssetEntry assetEntry = assetEntryLocalService.fetchEntry(
564                                    className, classPK);
565    
566                            deleteActivityCounters(assetEntry);
567                    }
568                    else {
569                            long classNameId = PortalUtil.getClassNameId(className);
570    
571                            socialActivityCounterPersistence.removeByC_C(classNameId, classPK);
572    
573                            socialActivityLimitPersistence.removeByUserId(classPK);
574                    }
575    
576                    clearFinderCache();
577            }
578    
579            /**
580             * Disables all the counters of an asset identified by the class name ID and
581             * class primary key.
582             *
583             * <p>
584             * This method is used by the recycle bin to disable all counters of assets
585             * put into the recycle bin. It adjusts the owner's contribution score.
586             * </p>
587             *
588             * @param  classNameId the primary key of the asset's class
589             * @param  classPK the primary key of the asset
590             * @throws PortalException if the asset owner's contribution counter could
591             *         not be updated
592             * @throws SystemException if a system exception occurred
593             */
594            public void disableActivityCounters(long classNameId, long classPK)
595                    throws PortalException, SystemException {
596    
597                    String className = PortalUtil.getClassName(classNameId);
598    
599                    disableActivityCounters(className, classPK);
600            }
601    
602            /**
603             * Disables all the counters of an asset identified by the class name and
604             * class primary key.
605             *
606             * <p>
607             * This method is used by the recycle bin to disable all counters of assets
608             * put into the recycle bin. It adjusts the owner's contribution score.
609             * </p>
610             *
611             * @param  className the asset's class name
612             * @param  classPK the primary key of the asset
613             * @throws PortalException if the asset owner's contribution counter could
614             *         not be updated
615             * @throws SystemException if a system exception occurred
616             */
617            public void disableActivityCounters(String className, long classPK)
618                    throws PortalException, SystemException {
619    
620                    AssetEntry assetEntry = assetEntryLocalService.fetchEntry(
621                            className, classPK);
622    
623                    if (assetEntry == null) {
624                            return;
625                    }
626    
627                    List<SocialActivityCounter> activityCounters =
628                            socialActivityCounterPersistence.findByC_C(
629                                    assetEntry.getClassNameId(), classPK);
630    
631                    adjustUserContribution(assetEntry, false);
632    
633                    for (SocialActivityCounter activityCounter : activityCounters) {
634                            if (activityCounter.isActive()) {
635                                    activityCounter.setActive(false);
636    
637                                    socialActivityCounterPersistence.update(activityCounter);
638                            }
639                    }
640    
641                    clearFinderCache();
642            }
643    
644            /**
645             * Enables all activity counters of an asset identified by the class name ID
646             * and class primary key.
647             *
648             * <p>
649             * This method is used by the recycle bin to enable all counters of assets
650             * restored from the recycle bin. It adjusts the owner's contribution score.
651             * </p>
652             *
653             * @param  classNameId the primary key of the asset's class
654             * @param  classPK the primary key of the asset
655             * @throws PortalException if the asset owner's contribution counter could
656             *         not be updated
657             * @throws SystemException if a system exception occurred
658             */
659            public void enableActivityCounters(long classNameId, long classPK)
660                    throws PortalException, SystemException {
661    
662                    String className = PortalUtil.getClassName(classNameId);
663    
664                    enableActivityCounters(className, classPK);
665            }
666    
667            /**
668             * Enables all the counters of an asset identified by the class name and
669             * class primary key.
670             *
671             * <p>
672             * This method is used by the recycle bin to enable all counters of assets
673             * restored from the recycle bin. It adjusts the owner's contribution score.
674             * </p>
675             *
676             * @param  className the asset's class name
677             * @param  classPK the primary key of the asset
678             * @throws PortalException if the asset owner's contribution counter could
679             *         not be updated
680             * @throws SystemException if a system exception occurred
681             */
682            public void enableActivityCounters(String className, long classPK)
683                    throws PortalException, SystemException {
684    
685                    AssetEntry assetEntry = assetEntryLocalService.fetchEntry(
686                            className, classPK);
687    
688                    if (assetEntry == null) {
689                            return;
690                    }
691    
692                    List<SocialActivityCounter> activityCounters =
693                            socialActivityCounterPersistence.findByC_C(
694                                    assetEntry.getClassNameId(), classPK);
695    
696                    adjustUserContribution(assetEntry, true);
697    
698                    for (SocialActivityCounter activityCounter : activityCounters) {
699                            if (!activityCounter.isActive()) {
700                                    activityCounter.setActive(true);
701    
702                                    socialActivityCounterPersistence.update(activityCounter);
703                            }
704                    }
705    
706                    clearFinderCache();
707            }
708    
709            /**
710             * Returns the activity counter with the given name, owner, and end period
711             * that belong to the given entity.
712             *
713             * @param  groupId the primary key of the group
714             * @param  classNameId the primary key of the entity's class
715             * @param  classPK the primary key of the entity
716             * @param  name the counter name
717             * @param  ownerType the owner type
718             * @param  endPeriod the end period, <code>-1</code> for the latest one
719             * @return the matching activity counter
720             * @throws SystemException if a system exception occurred
721             */
722            public SocialActivityCounter fetchActivityCounterByEndPeriod(
723                            long groupId, long classNameId, long classPK, String name,
724                            int ownerType, int endPeriod)
725                    throws SystemException {
726    
727                    return socialActivityCounterPersistence.fetchByG_C_C_N_O_E(
728                            groupId, classNameId, classPK, name, ownerType, endPeriod);
729            }
730    
731            /**
732             * Returns the activity counter with the given name, owner, and start period
733             * that belong to the given entity.
734             *
735             * @param  groupId the primary key of the group
736             * @param  classNameId the primary key of the entity's class
737             * @param  classPK the primary key of the entity
738             * @param  name the counter name
739             * @param  ownerType the owner type
740             * @param  startPeriod the start period
741             * @return the matching activity counter
742             * @throws SystemException if a system exception occurred
743             */
744            public SocialActivityCounter fetchActivityCounterByStartPeriod(
745                            long groupId, long classNameId, long classPK, String name,
746                            int ownerType, int startPeriod)
747                    throws SystemException {
748    
749                    return socialActivityCounterPersistence.fetchByG_C_C_N_O_S(
750                            groupId, classNameId, classPK, name, ownerType, startPeriod);
751            }
752    
753            /**
754             * Returns the latest activity counter with the given name and owner that
755             * belong to the given entity.
756             *
757             * @param  groupId the primary key of the group
758             * @param  classNameId the primary key of the entity's class
759             * @param  classPK the primary key of the entity
760             * @param  name the counter name
761             * @param  ownerType the owner type
762             * @return the matching activity counter
763             * @throws SystemException if a system exception occurred
764             */
765            public SocialActivityCounter fetchLatestActivityCounter(
766                            long groupId, long classNameId, long classPK, String name,
767                            int ownerType)
768                    throws SystemException {
769    
770                    return socialActivityCounterPersistence.fetchByG_C_C_N_O_E(
771                            groupId, classNameId, classPK, name, ownerType,
772                            SocialActivityCounterConstants.END_PERIOD_UNDEFINED);
773            }
774    
775            /**
776             * Returns all the activity counters with the given name and period offsets.
777             *
778             * <p>
779             * The start and end offsets can belong to different periods. This method
780             * groups the counters by name and returns the sum of their current values.
781             * </p>
782             *
783             * @param  groupId the primary key of the group
784             * @param  name the counter name
785             * @param  startOffset the offset for the start period
786             * @param  endOffset the offset for the end period
787             * @return the matching activity counters
788             * @throws SystemException if a system exception occurred
789             */
790            public List<SocialActivityCounter> getOffsetActivityCounters(
791                            long groupId, String name, int startOffset, int endOffset)
792                    throws SystemException {
793    
794                    int startPeriod = SocialCounterPeriodUtil.getStartPeriod(startOffset);
795                    int endPeriod = SocialCounterPeriodUtil.getEndPeriod(endOffset);
796    
797                    return getPeriodActivityCounters(groupId, name, startPeriod, endPeriod);
798            }
799    
800            /**
801             * Returns the distribution of the activity counters with the given name and
802             * period offsets.
803             *
804             * <p>
805             * The start and end offsets can belong to different periods. This method
806             * groups the counters by their owner entity (usually some asset) and
807             * returns a counter for each entity class with the sum of the counters'
808             * current values.
809             * </p>
810             *
811             * @param  groupId the primary key of the group
812             * @param  name the counter name
813             * @param  startOffset the offset for the start period
814             * @param  endOffset the offset for the end period
815             * @return the distribution of matching activity counters
816             * @throws SystemException if a system exception occurred
817             */
818            public List<SocialActivityCounter> getOffsetDistributionActivityCounters(
819                            long groupId, String name, int startOffset, int endOffset)
820                    throws SystemException {
821    
822                    int startPeriod = SocialCounterPeriodUtil.getStartPeriod(startOffset);
823                    int endPeriod = SocialCounterPeriodUtil.getEndPeriod(endOffset);
824    
825                    return getPeriodDistributionActivityCounters(
826                            groupId, name, startPeriod, endPeriod);
827            }
828    
829            /**
830             * Returns all the activity counters with the given name and time period.
831             *
832             * <p>
833             * The start and end period values can belong to different periods. This
834             * method groups the counters by name and returns the sum of their current
835             * values.
836             * </p>
837             *
838             * @param  groupId the primary key of the group
839             * @param  name the counter name
840             * @param  startPeriod the start period
841             * @param  endPeriod the end period
842             * @return the matching activity counters
843             * @throws SystemException if a system exception occurred
844             */
845            public List<SocialActivityCounter> getPeriodActivityCounters(
846                            long groupId, String name, int startPeriod, int endPeriod)
847                    throws SystemException {
848    
849                    if (endPeriod == SocialActivityCounterConstants.END_PERIOD_UNDEFINED) {
850                            endPeriod = SocialCounterPeriodUtil.getEndPeriod();
851                    }
852    
853                    int offset = SocialCounterPeriodUtil.getOffset(endPeriod);
854    
855                    int periodLength = SocialCounterPeriodUtil.getPeriodLength(offset);
856    
857                    return socialActivityCounterFinder.findAC_ByG_N_S_E_1(
858                            groupId, name, startPeriod, endPeriod, periodLength);
859            }
860    
861            /**
862             * Returns the distribution of activity counters with the given name and
863             * time period.
864             *
865             * <p>
866             * The start and end period values can belong to different periods. This
867             * method groups the counters by their owner entity (usually some asset) and
868             * returns a counter for each entity class with the sum of the counters'
869             * current values.
870             * </p>
871             *
872             * @param  groupId the primary key of the group
873             * @param  name the counter name
874             * @param  startPeriod the start period
875             * @param  endPeriod the end period
876             * @return the distribution of matching activity counters
877             * @throws SystemException if a system exception occurred
878             */
879            public List<SocialActivityCounter> getPeriodDistributionActivityCounters(
880                            long groupId, String name, int startPeriod, int endPeriod)
881                    throws SystemException {
882    
883                    int offset = SocialCounterPeriodUtil.getOffset(endPeriod);
884    
885                    int periodLength = SocialCounterPeriodUtil.getPeriodLength(offset);
886    
887                    return socialActivityCounterFinder.findAC_ByG_N_S_E_2(
888                            groupId, name, startPeriod, endPeriod, periodLength);
889            }
890    
891            /**
892             * Returns the range of tuples that contain users and a list of activity
893             * counters.
894             *
895             * <p>
896             * The counters returned for each user are passed to this method in the
897             * selectedNames array. The method also accepts an array of counter names
898             * that are used to rank the users.
899             * </p>
900             *
901             * <p>
902             * Useful when paginating results. Returns a maximum of <code>end -
903             * start</code> instances. <code>start</code> and <code>end</code> are not
904             * primary keys, they are indexes in the result set. Thus, <code>0</code>
905             * refers to the first result in the set. Setting both <code>start</code>
906             * and <code>end</code> to {@link
907             * com.liferay.portal.kernel.dao.orm.QueryUtil#ALL_POS} will return the full
908             * result set.
909             * </p>
910             *
911             * @param  groupId the primary key of the group
912             * @param  rankingNames the ranking counter names
913             * @param  selectedNames the counter names that will be returned with each
914             *         user
915             * @param  start the lower bound of the range of results
916             * @param  end the upper bound of the range of results (not inclusive)
917             * @return the range of matching tuples
918             * @throws SystemException if a system exception occurred
919             */
920            public List<Tuple> getUserActivityCounters(
921                            long groupId, String[] rankingNames, String[] selectedNames,
922                            int start, int end)
923                    throws SystemException {
924    
925                    List<Long> userIds = socialActivityCounterFinder.findU_ByG_N(
926                            groupId, rankingNames, start, end);
927    
928                    if (userIds.isEmpty()) {
929                            return Collections.emptyList();
930                    }
931    
932                    Tuple[] userActivityCounters = new Tuple[userIds.size()];
933    
934                    List<SocialActivityCounter> activityCounters =
935                            socialActivityCounterFinder.findAC_By_G_C_C_N_S_E(
936                                    groupId, userIds, selectedNames, QueryUtil.ALL_POS,
937                                    QueryUtil.ALL_POS);
938    
939                    long userId = 0;
940                    Map<String, SocialActivityCounter> activityCountersMap = null;
941    
942                    for (SocialActivityCounter activityCounter : activityCounters) {
943                            if (userId != activityCounter.getClassPK()) {
944                                    userId = activityCounter.getClassPK();
945                                    activityCountersMap =
946                                            new HashMap<String, SocialActivityCounter>();
947    
948                                    Tuple userActivityCounter = new Tuple(
949                                            userId, activityCountersMap);
950    
951                                    for (int i = 0; i < userIds.size(); i++) {
952                                            long curUserId = userIds.get(i);
953    
954                                            if (userId == curUserId) {
955                                                    userActivityCounters[i] = userActivityCounter;
956    
957                                                    break;
958                                            }
959                                    }
960                            }
961    
962                            activityCountersMap.put(activityCounter.getName(), activityCounter);
963                    }
964    
965                    return Arrays.asList(userActivityCounters);
966            }
967    
968            /**
969             * Returns the number of users having a rank based on the given counters.
970             *
971             * @param  groupId the primary key of the group
972             * @param  rankingNames the ranking counter names
973             * @return the number of matching users
974             * @throws SystemException if a system exception occurred
975             */
976            public int getUserActivityCountersCount(long groupId, String[] rankingNames)
977                    throws SystemException {
978    
979                    return socialActivityCounterFinder.countU_ByG_N(groupId, rankingNames);
980            }
981    
982            /**
983             * Increments the <code>user.achievements</code> counter for a user.
984             *
985             * <p>
986             * This method should be used by an external achievement class when the
987             * users unlocks an achievement.
988             * </p>
989             *
990             * @param  userId the primary key of the user
991             * @param  groupId the primary key of the group
992             * @throws PortalException if the group or an expected previous activity
993             *         counter could not be found
994             * @throws SystemException if a system exception occurred
995             */
996            public void incrementUserAchievementCounter(long userId, long groupId)
997                    throws PortalException, SystemException {
998    
999                    incrementActivityCounter(
1000                            groupId, PortalUtil.getClassNameId(User.class.getName()), userId,
1001                            SocialActivityCounterConstants.NAME_USER_ACHIEVEMENTS,
1002                            SocialActivityCounterConstants.TYPE_ACTOR, 1,
1003                            SocialActivityCounterConstants.PERIOD_LENGTH_SYSTEM);
1004            }
1005    
1006            protected boolean addActivityCounter(
1007                    User user, User assetEntryUser,
1008                    SocialActivityCounterDefinition activityCounterDefinition) {
1009    
1010                    if ((user.isDefaultUser() || !user.isActive()) &&
1011                            (activityCounterDefinition.getOwnerType() !=
1012                                    SocialActivityCounterConstants.TYPE_ASSET)) {
1013    
1014                            return false;
1015                    }
1016    
1017                    if ((assetEntryUser.isDefaultUser() || !assetEntryUser.isActive()) &&
1018                            (activityCounterDefinition.getOwnerType() !=
1019                                    SocialActivityCounterConstants.TYPE_ACTOR)) {
1020    
1021                            return false;
1022                    }
1023    
1024                    if (!activityCounterDefinition.isEnabled() ||
1025                            (activityCounterDefinition.getIncrement() == 0)) {
1026    
1027                            return false;
1028                    }
1029    
1030                    String name = activityCounterDefinition.getName();
1031    
1032                    if ((user.getUserId() == assetEntryUser.getUserId()) &&
1033                            (name.equals(SocialActivityCounterConstants.NAME_CONTRIBUTION) ||
1034                             name.equals(SocialActivityCounterConstants.NAME_POPULARITY))) {
1035    
1036                            return false;
1037                    }
1038    
1039                    return true;
1040            }
1041    
1042            protected void adjustUserContribution(AssetEntry assetEntry, boolean enable)
1043                    throws PortalException, SystemException {
1044    
1045                    if (assetEntry == null) {
1046                            return;
1047                    }
1048    
1049                    SocialActivityCounter latestPopularityActivityCounter =
1050                            fetchLatestActivityCounter(
1051                                    assetEntry.getGroupId(), assetEntry.getClassNameId(),
1052                                    assetEntry.getClassPK(),
1053                                    SocialActivityCounterConstants.NAME_POPULARITY,
1054                                    SocialActivityCounterConstants.TYPE_ASSET);
1055    
1056                    if ((latestPopularityActivityCounter == null) ||
1057                            (enable && latestPopularityActivityCounter.isActive()) ||
1058                            (!enable && !latestPopularityActivityCounter.isActive())) {
1059    
1060                            return;
1061                    }
1062    
1063                    int factor = -1;
1064    
1065                    if (enable) {
1066                            factor = 1;
1067                    }
1068    
1069                    SocialActivityCounter latestContributionActivityCounter =
1070                            fetchLatestActivityCounter(
1071                                    assetEntry.getGroupId(),
1072                                    PortalUtil.getClassNameId(User.class.getName()),
1073                                    assetEntry.getUserId(),
1074                                    SocialActivityCounterConstants.NAME_CONTRIBUTION,
1075                                    SocialActivityCounterConstants.TYPE_CREATOR);
1076    
1077                    if (latestContributionActivityCounter == null) {
1078                            return;
1079                    }
1080    
1081                    int startPeriod = SocialCounterPeriodUtil.getStartPeriod();
1082    
1083                    if (latestContributionActivityCounter.getStartPeriod() != startPeriod) {
1084                            latestContributionActivityCounter = addActivityCounter(
1085                                    latestContributionActivityCounter.getGroupId(),
1086                                    latestContributionActivityCounter.getClassNameId(),
1087                                    latestContributionActivityCounter.getClassPK(),
1088                                    latestContributionActivityCounter.getName(),
1089                                    latestContributionActivityCounter.getOwnerType(), 0,
1090                                    latestContributionActivityCounter.getTotalValue(),
1091                                    SocialCounterPeriodUtil.getStartPeriod(),
1092                                    SocialActivityCounterConstants.END_PERIOD_UNDEFINED,
1093                                    latestContributionActivityCounter.getActivityCounterId(),
1094                                    SocialActivityCounterConstants.PERIOD_LENGTH_SYSTEM);
1095                    }
1096    
1097                    if (latestPopularityActivityCounter.getStartPeriod() == startPeriod) {
1098                            latestContributionActivityCounter.setCurrentValue(
1099                                    latestContributionActivityCounter.getCurrentValue() +
1100                                            (latestPopularityActivityCounter.getCurrentValue() *
1101                                                    factor));
1102                    }
1103    
1104                    latestContributionActivityCounter.setTotalValue(
1105                            latestContributionActivityCounter.getTotalValue() +
1106                                    (latestPopularityActivityCounter.getTotalValue() * factor));
1107    
1108                    socialActivityCounterPersistence.update(
1109                            latestContributionActivityCounter);
1110            }
1111    
1112            protected boolean checkActivityLimit(
1113                            User user, SocialActivity activity,
1114                            SocialActivityCounterDefinition activityCounterDefinition)
1115                    throws PortalException, SystemException {
1116    
1117                    if (activityCounterDefinition.getLimitValue() == 0) {
1118                            return true;
1119                    }
1120    
1121                    long classPK = activity.getClassPK();
1122    
1123                    String name = activityCounterDefinition.getName();
1124    
1125                    if (name.equals(SocialActivityCounterConstants.NAME_PARTICIPATION)) {
1126                            classPK = 0;
1127                    }
1128    
1129                    SocialActivityLimit activityLimit =
1130                            socialActivityLimitPersistence.fetchByG_U_C_C_A_A(
1131                                    activity.getGroupId(), user.getUserId(),
1132                                    activity.getClassNameId(), classPK, activity.getType(),
1133                                    activityCounterDefinition.getName());
1134    
1135                    if (activityLimit == null) {
1136                            try {
1137                                    activityLimit =
1138                                            socialActivityLimitLocalService.addActivityLimit(
1139                                                    user.getUserId(), activity.getGroupId(),
1140                                                    activity.getClassNameId(), classPK, activity.getType(),
1141                                                    activityCounterDefinition.getName(),
1142                                                    activityCounterDefinition.getLimitPeriod());
1143                            }
1144                            catch (SystemException se) {
1145                                    activityLimit =
1146                                            socialActivityLimitPersistence.fetchByG_U_C_C_A_A(
1147                                                    activity.getGroupId(), user.getUserId(),
1148                                                    activity.getClassNameId(), classPK, activity.getType(),
1149                                                    activityCounterDefinition.getName());
1150    
1151                                    if (activityLimit == null) {
1152                                            throw se;
1153                                    }
1154                            }
1155                    }
1156    
1157                    int count = activityLimit.getCount(
1158                            activityCounterDefinition.getLimitPeriod());
1159    
1160                    if (count < activityCounterDefinition.getLimitValue()) {
1161                            activityLimit.setCount(
1162                                    activityCounterDefinition.getLimitPeriod(), count + 1);
1163    
1164                            socialActivityLimitPersistence.update(activityLimit);
1165    
1166                            return true;
1167                    }
1168    
1169                    return false;
1170            }
1171    
1172            protected void clearFinderCache() {
1173                    PortalCache<String, SocialActivityCounter> portalCache =
1174                            MultiVMPoolUtil.getCache(
1175                                    SocialActivityCounterFinder.class.getName());
1176    
1177                    portalCache.removeAll();
1178            }
1179    
1180            protected String getLockKey(
1181                    long groupId, long classNameId, long classPK, String name,
1182                    int ownerType) {
1183    
1184                    StringBundler sb = new StringBundler(7);
1185    
1186                    sb.append(StringUtil.toHexString(groupId));
1187                    sb.append(StringPool.POUND);
1188                    sb.append(StringUtil.toHexString(classNameId));
1189                    sb.append(StringPool.POUND);
1190                    sb.append(StringUtil.toHexString(classPK));
1191                    sb.append(StringPool.POUND);
1192                    sb.append(name);
1193    
1194                    return sb.toString();
1195            }
1196    
1197            protected void incrementActivityCounter(
1198                            long groupId, long classNameId, long classPK, String name,
1199                            int ownerType, int increment, int periodLength)
1200                    throws PortalException, SystemException {
1201    
1202                    SocialActivityCounter activityCounter = fetchLatestActivityCounter(
1203                            groupId, classNameId, classPK, name, ownerType);
1204    
1205                    if (activityCounter == null) {
1206                            activityCounter = addActivityCounter(
1207                                    groupId, classNameId, classPK, name, ownerType, 0, 0,
1208                                    SocialCounterPeriodUtil.getStartPeriod(),
1209                                    SocialActivityCounterConstants.END_PERIOD_UNDEFINED);
1210    
1211                            if (periodLength > 0) {
1212                                    activityCounter.setStartPeriod(
1213                                            SocialCounterPeriodUtil.getActivityDay());
1214                            }
1215                    }
1216    
1217                    if (!activityCounter.isActivePeriod(periodLength)) {
1218                            activityCounter = addActivityCounter(
1219                                    activityCounter.getGroupId(), activityCounter.getClassNameId(),
1220                                    activityCounter.getClassPK(), activityCounter.getName(),
1221                                    activityCounter.getOwnerType(), 0,
1222                                    activityCounter.getTotalValue(),
1223                                    SocialCounterPeriodUtil.getStartPeriod(),
1224                                    SocialActivityCounterConstants.END_PERIOD_UNDEFINED,
1225                                    activityCounter.getActivityCounterId(), periodLength);
1226                    }
1227    
1228                    activityCounter.setCurrentValue(
1229                            activityCounter.getCurrentValue() + increment);
1230                    activityCounter.setTotalValue(
1231                            activityCounter.getTotalValue() + increment);
1232    
1233                    socialActivityCounterPersistence.update(activityCounter);
1234            }
1235    
1236            protected void incrementActivityCounter(
1237                            long groupId, User user, AssetEntry assetEntry,
1238                            SocialActivityCounterDefinition activityCounterDefinition)
1239                    throws PortalException, SystemException {
1240    
1241                    int ownerType = activityCounterDefinition.getOwnerType();
1242                    long userClassNameId = PortalUtil.getClassNameId(User.class.getName());
1243    
1244                    if (ownerType == SocialActivityCounterConstants.TYPE_ACTOR) {
1245                            incrementActivityCounter(
1246                                    groupId, userClassNameId, user.getUserId(),
1247                                    activityCounterDefinition.getName(), ownerType,
1248                                    activityCounterDefinition.getIncrement(),
1249                                    activityCounterDefinition.getPeriodLength());
1250                    }
1251                    else if (ownerType == SocialActivityCounterConstants.TYPE_ASSET) {
1252                            incrementActivityCounter(
1253                                    groupId, assetEntry.getClassNameId(), assetEntry.getClassPK(),
1254                                    activityCounterDefinition.getName(), ownerType,
1255                                    activityCounterDefinition.getIncrement(),
1256                                    activityCounterDefinition.getPeriodLength());
1257                    }
1258                    else {
1259                            incrementActivityCounter(
1260                                    groupId, userClassNameId, assetEntry.getUserId(),
1261                                    activityCounterDefinition.getName(), ownerType,
1262                                    activityCounterDefinition.getIncrement(),
1263                                    activityCounterDefinition.getPeriodLength());
1264                    }
1265            }
1266    
1267            private static Log _log = LogFactoryUtil.getLog(
1268                    SocialActivityCounterLocalService.class);
1269    
1270    }