001    /**
002     * Copyright (c) 2000-2013 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) ||
296                            !activityDefinition.isCountersEnabled()) {
297    
298                            return;
299                    }
300    
301                    SocialActivityProcessor activityProcessor =
302                            activityDefinition.getActivityProcessor();
303    
304                    if (activityProcessor != null) {
305                            activityProcessor.processActivity(activity);
306                    }
307    
308                    AssetEntry assetEntry = activity.getAssetEntry();
309    
310                    User assetEntryUser = userPersistence.findByPrimaryKey(
311                            assetEntry.getUserId());
312    
313                    for (SocialActivityCounterDefinition activityCounterDefinition :
314                                    activityDefinition.getActivityCounterDefinitions()) {
315    
316                            if (addActivityCounter(
317                                            user, assetEntryUser, activityCounterDefinition) &&
318                                    checkActivityLimit(user, activity, activityCounterDefinition)) {
319    
320                                    incrementActivityCounter(
321                                            activity.getGroupId(), user, activity.getAssetEntry(),
322                                            activityCounterDefinition);
323                            }
324                    }
325    
326                    for (SocialAchievement achievement :
327                                    activityDefinition.getAchievements()) {
328    
329                            achievement.processActivity(activity);
330                    }
331    
332                    if (!user.isDefaultUser() && user.isActive()) {
333                            incrementActivityCounter(
334                                    activity.getGroupId(),
335                                    PortalUtil.getClassNameId(User.class.getName()),
336                                    activity.getUserId(),
337                                    SocialActivityCounterConstants.NAME_USER_ACTIVITIES,
338                                    SocialActivityCounterConstants.TYPE_ACTOR, 1,
339                                    SocialActivityCounterConstants.PERIOD_LENGTH_SYSTEM);
340                    }
341    
342                    if (!assetEntryUser.isDefaultUser() && assetEntryUser.isActive()) {
343                            incrementActivityCounter(
344                                    activity.getGroupId(), activity.getClassNameId(),
345                                    activity.getClassPK(),
346                                    SocialActivityCounterConstants.NAME_ASSET_ACTIVITIES,
347                                    SocialActivityCounterConstants.TYPE_ASSET, 1,
348                                    SocialActivityCounterConstants.PERIOD_LENGTH_SYSTEM);
349                    }
350            }
351    
352            /**
353             * Creates an activity counter with a default period length, adding it into
354             * the database.
355             *
356             * @param      groupId the primary key of the group
357             * @param      classNameId the primary key of the entity's class this
358             *             counter belongs to
359             * @param      classPK the primary key of the entity this counter belongs to
360             * @param      name the counter's name
361             * @param      ownerType the counter's owner type. Acceptable values are
362             *             <code>TYPE_ACTOR</code>, <code>TYPE_ASSET</code> and
363             *             <code>TYPE_CREATOR</code> defined in {@link
364             *             com.liferay.portlet.social.model.SocialActivityCounterConstants}.
365             * @param      currentValue the counter's current value (optionally
366             *             <code>0</code>)
367             * @param      totalValue the counter's total value (optionally
368             *             <code>0</code>)
369             * @param      startPeriod the counter's start period
370             * @param      endPeriod the counter's end period
371             * @return     the created activity counter
372             * @throws     PortalException if the group or a previous activity counter
373             *             could not be found
374             * @throws     SystemException if a system exception occurred
375             * @deprecated As of 6.2.0, replaced by {@link #createActivityCounter(long,
376             *             long, long, String, int, int, int, int, int, long, int)}
377             */
378            @Transactional(propagation = Propagation.REQUIRES_NEW)
379            public SocialActivityCounter createActivityCounter(
380                            long groupId, long classNameId, long classPK, String name,
381                            int ownerType, int currentValue, int totalValue, int startPeriod,
382                            int endPeriod)
383                    throws PortalException, SystemException {
384    
385                    return createActivityCounter(
386                            groupId, classNameId, classPK, name, ownerType, currentValue,
387                            totalValue, startPeriod, endPeriod, 0, 0);
388            }
389    
390            /**
391             * Creates an activity counter, adding it into the database.
392             *
393             * <p>
394             * This method actually creates the counter in the database. It requires a
395             * new transaction so that other threads can find the new counter when the
396             * lock in the calling method is released.
397             * </p>
398             *
399             * @param  groupId the primary key of the group
400             * @param  classNameId the primary key of the entity's class this counter
401             *         belongs to
402             * @param  classPK the primary key of the entity this counter belongs to
403             * @param  name the counter's name
404             * @param  ownerType the counter's owner type. Acceptable values are
405             *         <code>TYPE_ACTOR</code>, <code>TYPE_ASSET</code> and
406             *         <code>TYPE_CREATOR</code> defined in {@link
407             *         com.liferay.portlet.social.model.SocialActivityCounterConstants}.
408             * @param  currentValue the counter's current value (optionally
409             *         <code>0</code>)
410             * @param  totalValue the counter's total value of the counter (optionally
411             *         <code>0</code>)
412             * @param  startPeriod the counter's start period
413             * @param  endPeriod the counter's end period
414             * @param  previousActivityCounterId the primary key of the activity counter
415             *         for the previous time period (optionally <code>0</code>, if this
416             *         is the first)
417             * @param  periodLength the period length in days,
418             *         <code>PERIOD_LENGTH_INFINITE</code> for never ending counters or
419             *         <code>PERIOD_LENGTH_SYSTEM</code> for the period length defined
420             *         in <code>portal-ext.properties</code>. For more information see
421             *         {@link com.liferay.portlet.social.model.SocialActivityConstants}.
422             * @return the created activity counter
423             * @throws PortalException if the group or the previous activity counter
424             *         could not be found
425             * @throws SystemException if a system exception occurred
426             */
427            @Transactional(propagation = Propagation.REQUIRES_NEW)
428            public SocialActivityCounter createActivityCounter(
429                            long groupId, long classNameId, long classPK, String name,
430                            int ownerType, int currentValue, int totalValue, int startPeriod,
431                            int endPeriod, long previousActivityCounterId, int periodLength)
432                    throws PortalException, SystemException {
433    
434                    SocialActivityCounter activityCounter = null;
435    
436                    if (previousActivityCounterId != 0) {
437                            activityCounter = socialActivityCounterPersistence.findByPrimaryKey(
438                                    previousActivityCounterId);
439    
440                            if (periodLength ==
441                                            SocialActivityCounterConstants.PERIOD_LENGTH_SYSTEM) {
442    
443                                    activityCounter.setEndPeriod(
444                                            SocialCounterPeriodUtil.getStartPeriod() - 1);
445                            }
446                            else {
447                                    activityCounter.setEndPeriod(
448                                            activityCounter.getStartPeriod() + periodLength - 1);
449                            }
450    
451                            socialActivityCounterPersistence.update(activityCounter);
452                    }
453    
454                    activityCounter = socialActivityCounterPersistence.fetchByG_C_C_N_O_E(
455                            groupId, classNameId, classPK, name, ownerType, endPeriod, false);
456    
457                    if (activityCounter != null) {
458                            return activityCounter;
459                    }
460    
461                    Group group = groupPersistence.findByPrimaryKey(groupId);
462    
463                    long activityCounterId = counterLocalService.increment();
464    
465                    activityCounter = socialActivityCounterPersistence.create(
466                            activityCounterId);
467    
468                    activityCounter.setGroupId(groupId);
469                    activityCounter.setCompanyId(group.getCompanyId());
470                    activityCounter.setClassNameId(classNameId);
471                    activityCounter.setClassPK(classPK);
472                    activityCounter.setName(name);
473                    activityCounter.setOwnerType(ownerType);
474                    activityCounter.setCurrentValue(currentValue);
475                    activityCounter.setTotalValue(totalValue);
476                    activityCounter.setStartPeriod(startPeriod);
477                    activityCounter.setEndPeriod(endPeriod);
478                    activityCounter.setActive(true);
479    
480                    socialActivityCounterPersistence.update(activityCounter);
481    
482                    return activityCounter;
483            }
484    
485            /**
486             * Deletes all activity counters, limits, and settings related to the asset.
487             *
488             * <p>
489             * This method subtracts the asset's popularity from the owner's
490             * contribution points. It also creates a new contribution period if the
491             * latest one does not belong to the current period.
492             * </p>
493             *
494             * @param  assetEntry the asset entry
495             * @throws PortalException if the new contribution counter could not be
496             *         created
497             * @throws SystemException if a system exception occurred
498             */
499            public void deleteActivityCounters(AssetEntry assetEntry)
500                    throws PortalException, SystemException {
501    
502                    if (assetEntry == null) {
503                            return;
504                    }
505    
506                    adjustUserContribution(assetEntry, false);
507    
508                    socialActivityCounterPersistence.removeByC_C(
509                            assetEntry.getClassNameId(), assetEntry.getClassPK());
510    
511                    socialActivityLimitPersistence.removeByC_C(
512                            assetEntry.getClassNameId(), assetEntry.getClassPK());
513    
514                    socialActivitySettingLocalService.deleteActivitySetting(
515                            assetEntry.getGroupId(), assetEntry.getClassName(),
516                            assetEntry.getClassPK());
517    
518                    clearFinderCache();
519            }
520    
521            /**
522             * Deletes all activity counters, limits, and settings related to the entity
523             * identified by the class name ID and class primary key.
524             *
525             * @param  classNameId the primary key of the entity's class
526             * @param  classPK the primary key of the entity
527             * @throws PortalException if the entity is an asset and its owner's
528             *         contribution counter could not be updated
529             * @throws SystemException if a system exception occurred
530             */
531            public void deleteActivityCounters(long classNameId, long classPK)
532                    throws PortalException, SystemException {
533    
534                    String className = PortalUtil.getClassName(classNameId);
535    
536                    if (!className.equals(User.class.getName())) {
537                            AssetEntry assetEntry = assetEntryLocalService.fetchEntry(
538                                    className, classPK);
539    
540                            deleteActivityCounters(assetEntry);
541                    }
542                    else {
543                            socialActivityCounterPersistence.removeByC_C(classNameId, classPK);
544    
545                            socialActivityLimitPersistence.removeByUserId(classPK);
546                    }
547    
548                    clearFinderCache();
549            }
550    
551            /**
552             * Deletes all activity counters for the entity identified by the class name
553             * and class primary key.
554             *
555             * @param  className the entity's class name
556             * @param  classPK the primary key of the entity
557             * @throws PortalException if the entity is an asset and its owner's
558             *         contribution counter could not be updated
559             * @throws SystemException if a system exception occurred
560             */
561            public void deleteActivityCounters(String className, long classPK)
562                    throws PortalException, SystemException {
563    
564                    if (!className.equals(User.class.getName())) {
565                            AssetEntry assetEntry = assetEntryLocalService.fetchEntry(
566                                    className, classPK);
567    
568                            deleteActivityCounters(assetEntry);
569                    }
570                    else {
571                            long classNameId = PortalUtil.getClassNameId(className);
572    
573                            socialActivityCounterPersistence.removeByC_C(classNameId, classPK);
574    
575                            socialActivityLimitPersistence.removeByUserId(classPK);
576                    }
577    
578                    clearFinderCache();
579            }
580    
581            /**
582             * Disables all the counters of an asset identified by the class name ID and
583             * class primary key.
584             *
585             * <p>
586             * This method is used by the recycle bin to disable all counters of assets
587             * put into the recycle bin. It adjusts the owner's contribution score.
588             * </p>
589             *
590             * @param  classNameId the primary key of the asset's class
591             * @param  classPK the primary key of the asset
592             * @throws PortalException if the asset owner's contribution counter could
593             *         not be updated
594             * @throws SystemException if a system exception occurred
595             */
596            public void disableActivityCounters(long classNameId, long classPK)
597                    throws PortalException, SystemException {
598    
599                    String className = PortalUtil.getClassName(classNameId);
600    
601                    disableActivityCounters(className, classPK);
602            }
603    
604            /**
605             * Disables all the counters of an asset identified by the class name and
606             * class primary key.
607             *
608             * <p>
609             * This method is used by the recycle bin to disable all counters of assets
610             * put into the recycle bin. It adjusts the owner's contribution score.
611             * </p>
612             *
613             * @param  className the asset's class name
614             * @param  classPK the primary key of the asset
615             * @throws PortalException if the asset owner's contribution counter could
616             *         not be updated
617             * @throws SystemException if a system exception occurred
618             */
619            public void disableActivityCounters(String className, long classPK)
620                    throws PortalException, SystemException {
621    
622                    AssetEntry assetEntry = assetEntryLocalService.fetchEntry(
623                            className, classPK);
624    
625                    if (assetEntry == null) {
626                            return;
627                    }
628    
629                    List<SocialActivityCounter> activityCounters =
630                            socialActivityCounterPersistence.findByC_C(
631                                    assetEntry.getClassNameId(), classPK);
632    
633                    adjustUserContribution(assetEntry, false);
634    
635                    for (SocialActivityCounter activityCounter : activityCounters) {
636                            if (activityCounter.isActive()) {
637                                    activityCounter.setActive(false);
638    
639                                    socialActivityCounterPersistence.update(activityCounter);
640                            }
641                    }
642    
643                    clearFinderCache();
644            }
645    
646            /**
647             * Enables all activity counters of an asset identified by the class name ID
648             * and class primary key.
649             *
650             * <p>
651             * This method is used by the recycle bin to enable all counters of assets
652             * restored from the recycle bin. It adjusts the owner's contribution score.
653             * </p>
654             *
655             * @param  classNameId the primary key of the asset's class
656             * @param  classPK the primary key of the asset
657             * @throws PortalException if the asset owner's contribution counter could
658             *         not be updated
659             * @throws SystemException if a system exception occurred
660             */
661            public void enableActivityCounters(long classNameId, long classPK)
662                    throws PortalException, SystemException {
663    
664                    String className = PortalUtil.getClassName(classNameId);
665    
666                    enableActivityCounters(className, classPK);
667            }
668    
669            /**
670             * Enables all the counters of an asset identified by the class name and
671             * class primary key.
672             *
673             * <p>
674             * This method is used by the recycle bin to enable all counters of assets
675             * restored from the recycle bin. It adjusts the owner's contribution score.
676             * </p>
677             *
678             * @param  className the asset's class name
679             * @param  classPK the primary key of the asset
680             * @throws PortalException if the asset owner's contribution counter could
681             *         not be updated
682             * @throws SystemException if a system exception occurred
683             */
684            public void enableActivityCounters(String className, long classPK)
685                    throws PortalException, SystemException {
686    
687                    AssetEntry assetEntry = assetEntryLocalService.fetchEntry(
688                            className, classPK);
689    
690                    if (assetEntry == null) {
691                            return;
692                    }
693    
694                    List<SocialActivityCounter> activityCounters =
695                            socialActivityCounterPersistence.findByC_C(
696                                    assetEntry.getClassNameId(), classPK);
697    
698                    adjustUserContribution(assetEntry, true);
699    
700                    for (SocialActivityCounter activityCounter : activityCounters) {
701                            if (!activityCounter.isActive()) {
702                                    activityCounter.setActive(true);
703    
704                                    socialActivityCounterPersistence.update(activityCounter);
705                            }
706                    }
707    
708                    clearFinderCache();
709            }
710    
711            /**
712             * Returns the activity counter with the given name, owner, and end period
713             * that belong to the given entity.
714             *
715             * @param  groupId the primary key of the group
716             * @param  classNameId the primary key of the entity's class
717             * @param  classPK the primary key of the entity
718             * @param  name the counter name
719             * @param  ownerType the owner type
720             * @param  endPeriod the end period, <code>-1</code> for the latest one
721             * @return the matching activity counter
722             * @throws SystemException if a system exception occurred
723             */
724            public SocialActivityCounter fetchActivityCounterByEndPeriod(
725                            long groupId, long classNameId, long classPK, String name,
726                            int ownerType, int endPeriod)
727                    throws SystemException {
728    
729                    return socialActivityCounterPersistence.fetchByG_C_C_N_O_E(
730                            groupId, classNameId, classPK, name, ownerType, endPeriod);
731            }
732    
733            /**
734             * Returns the activity counter with the given name, owner, and start period
735             * that belong to the given entity.
736             *
737             * @param  groupId the primary key of the group
738             * @param  classNameId the primary key of the entity's class
739             * @param  classPK the primary key of the entity
740             * @param  name the counter name
741             * @param  ownerType the owner type
742             * @param  startPeriod the start period
743             * @return the matching activity counter
744             * @throws SystemException if a system exception occurred
745             */
746            public SocialActivityCounter fetchActivityCounterByStartPeriod(
747                            long groupId, long classNameId, long classPK, String name,
748                            int ownerType, int startPeriod)
749                    throws SystemException {
750    
751                    return socialActivityCounterPersistence.fetchByG_C_C_N_O_S(
752                            groupId, classNameId, classPK, name, ownerType, startPeriod);
753            }
754    
755            /**
756             * Returns the latest activity counter with the given name and owner that
757             * belong to the given entity.
758             *
759             * @param  groupId the primary key of the group
760             * @param  classNameId the primary key of the entity's class
761             * @param  classPK the primary key of the entity
762             * @param  name the counter name
763             * @param  ownerType the owner type
764             * @return the matching activity counter
765             * @throws SystemException if a system exception occurred
766             */
767            public SocialActivityCounter fetchLatestActivityCounter(
768                            long groupId, long classNameId, long classPK, String name,
769                            int ownerType)
770                    throws SystemException {
771    
772                    return socialActivityCounterPersistence.fetchByG_C_C_N_O_E(
773                            groupId, classNameId, classPK, name, ownerType,
774                            SocialActivityCounterConstants.END_PERIOD_UNDEFINED);
775            }
776    
777            /**
778             * Returns all the activity counters with the given name and period offsets.
779             *
780             * <p>
781             * The start and end offsets can belong to different periods. This method
782             * groups the counters by name and returns the sum of their current values.
783             * </p>
784             *
785             * @param  groupId the primary key of the group
786             * @param  name the counter name
787             * @param  startOffset the offset for the start period
788             * @param  endOffset the offset for the end period
789             * @return the matching activity counters
790             * @throws SystemException if a system exception occurred
791             */
792            public List<SocialActivityCounter> getOffsetActivityCounters(
793                            long groupId, String name, int startOffset, int endOffset)
794                    throws SystemException {
795    
796                    int startPeriod = SocialCounterPeriodUtil.getStartPeriod(startOffset);
797                    int endPeriod = SocialCounterPeriodUtil.getEndPeriod(endOffset);
798    
799                    return getPeriodActivityCounters(groupId, name, startPeriod, endPeriod);
800            }
801    
802            /**
803             * Returns the distribution of the activity counters with the given name and
804             * period offsets.
805             *
806             * <p>
807             * The start and end offsets can belong to different periods. This method
808             * groups the counters by their owner entity (usually some asset) and
809             * returns a counter for each entity class with the sum of the counters'
810             * current values.
811             * </p>
812             *
813             * @param  groupId the primary key of the group
814             * @param  name the counter name
815             * @param  startOffset the offset for the start period
816             * @param  endOffset the offset for the end period
817             * @return the distribution of matching activity counters
818             * @throws SystemException if a system exception occurred
819             */
820            public List<SocialActivityCounter> getOffsetDistributionActivityCounters(
821                            long groupId, String name, int startOffset, int endOffset)
822                    throws SystemException {
823    
824                    int startPeriod = SocialCounterPeriodUtil.getStartPeriod(startOffset);
825                    int endPeriod = SocialCounterPeriodUtil.getEndPeriod(endOffset);
826    
827                    return getPeriodDistributionActivityCounters(
828                            groupId, name, startPeriod, endPeriod);
829            }
830    
831            /**
832             * Returns all the activity counters with the given name and time period.
833             *
834             * <p>
835             * The start and end period values can belong to different periods. This
836             * method groups the counters by name and returns the sum of their current
837             * values.
838             * </p>
839             *
840             * @param  groupId the primary key of the group
841             * @param  name the counter name
842             * @param  startPeriod the start period
843             * @param  endPeriod the end period
844             * @return the matching activity counters
845             * @throws SystemException if a system exception occurred
846             */
847            public List<SocialActivityCounter> getPeriodActivityCounters(
848                            long groupId, String name, int startPeriod, int endPeriod)
849                    throws SystemException {
850    
851                    if (endPeriod == SocialActivityCounterConstants.END_PERIOD_UNDEFINED) {
852                            endPeriod = SocialCounterPeriodUtil.getEndPeriod();
853                    }
854    
855                    int offset = SocialCounterPeriodUtil.getOffset(endPeriod);
856    
857                    int periodLength = SocialCounterPeriodUtil.getPeriodLength(offset);
858    
859                    return socialActivityCounterFinder.findAC_ByG_N_S_E_1(
860                            groupId, name, startPeriod, endPeriod, periodLength);
861            }
862    
863            /**
864             * Returns the distribution of activity counters with the given name and
865             * time period.
866             *
867             * <p>
868             * The start and end period values can belong to different periods. This
869             * method groups the counters by their owner entity (usually some asset) and
870             * returns a counter for each entity class with the sum of the counters'
871             * current values.
872             * </p>
873             *
874             * @param  groupId the primary key of the group
875             * @param  name the counter name
876             * @param  startPeriod the start period
877             * @param  endPeriod the end period
878             * @return the distribution of matching activity counters
879             * @throws SystemException if a system exception occurred
880             */
881            public List<SocialActivityCounter> getPeriodDistributionActivityCounters(
882                            long groupId, String name, int startPeriod, int endPeriod)
883                    throws SystemException {
884    
885                    int offset = SocialCounterPeriodUtil.getOffset(endPeriod);
886    
887                    int periodLength = SocialCounterPeriodUtil.getPeriodLength(offset);
888    
889                    return socialActivityCounterFinder.findAC_ByG_N_S_E_2(
890                            groupId, name, startPeriod, endPeriod, periodLength);
891            }
892    
893            /**
894             * Returns the range of tuples that contain users and a list of activity
895             * counters.
896             *
897             * <p>
898             * The counters returned for each user are passed to this method in the
899             * selectedNames array. The method also accepts an array of counter names
900             * that are used to rank the users.
901             * </p>
902             *
903             * <p>
904             * Useful when paginating results. Returns a maximum of <code>end -
905             * start</code> instances. <code>start</code> and <code>end</code> are not
906             * primary keys, they are indexes in the result set. Thus, <code>0</code>
907             * refers to the first result in the set. Setting both <code>start</code>
908             * and <code>end</code> to {@link
909             * com.liferay.portal.kernel.dao.orm.QueryUtil#ALL_POS} will return the full
910             * result set.
911             * </p>
912             *
913             * @param  groupId the primary key of the group
914             * @param  rankingNames the ranking counter names
915             * @param  selectedNames the counter names that will be returned with each
916             *         user
917             * @param  start the lower bound of the range of results
918             * @param  end the upper bound of the range of results (not inclusive)
919             * @return the range of matching tuples
920             * @throws SystemException if a system exception occurred
921             */
922            public List<Tuple> getUserActivityCounters(
923                            long groupId, String[] rankingNames, String[] selectedNames,
924                            int start, int end)
925                    throws SystemException {
926    
927                    List<Long> userIds = socialActivityCounterFinder.findU_ByG_N(
928                            groupId, rankingNames, start, end);
929    
930                    if (userIds.isEmpty()) {
931                            return Collections.emptyList();
932                    }
933    
934                    Tuple[] userActivityCounters = new Tuple[userIds.size()];
935    
936                    List<SocialActivityCounter> activityCounters =
937                            socialActivityCounterFinder.findAC_By_G_C_C_N_S_E(
938                                    groupId, userIds, selectedNames, QueryUtil.ALL_POS,
939                                    QueryUtil.ALL_POS);
940    
941                    long userId = 0;
942                    Map<String, SocialActivityCounter> activityCountersMap = null;
943    
944                    for (SocialActivityCounter activityCounter : activityCounters) {
945                            if (userId != activityCounter.getClassPK()) {
946                                    userId = activityCounter.getClassPK();
947                                    activityCountersMap =
948                                            new HashMap<String, SocialActivityCounter>();
949    
950                                    Tuple userActivityCounter = new Tuple(
951                                            userId, activityCountersMap);
952    
953                                    for (int i = 0; i < userIds.size(); i++) {
954                                            long curUserId = userIds.get(i);
955    
956                                            if (userId == curUserId) {
957                                                    userActivityCounters[i] = userActivityCounter;
958    
959                                                    break;
960                                            }
961                                    }
962                            }
963    
964                            activityCountersMap.put(activityCounter.getName(), activityCounter);
965                    }
966    
967                    return Arrays.asList(userActivityCounters);
968            }
969    
970            /**
971             * Returns the number of users having a rank based on the given counters.
972             *
973             * @param  groupId the primary key of the group
974             * @param  rankingNames the ranking counter names
975             * @return the number of matching users
976             * @throws SystemException if a system exception occurred
977             */
978            public int getUserActivityCountersCount(long groupId, String[] rankingNames)
979                    throws SystemException {
980    
981                    return socialActivityCounterFinder.countU_ByG_N(groupId, rankingNames);
982            }
983    
984            /**
985             * Increments the <code>user.achievements</code> counter for a user.
986             *
987             * <p>
988             * This method should be used by an external achievement class when the
989             * users unlocks an achievement.
990             * </p>
991             *
992             * @param  userId the primary key of the user
993             * @param  groupId the primary key of the group
994             * @throws PortalException if the group or an expected previous activity
995             *         counter could not be found
996             * @throws SystemException if a system exception occurred
997             */
998            public void incrementUserAchievementCounter(long userId, long groupId)
999                    throws PortalException, SystemException {
1000    
1001                    incrementActivityCounter(
1002                            groupId, PortalUtil.getClassNameId(User.class.getName()), userId,
1003                            SocialActivityCounterConstants.NAME_USER_ACHIEVEMENTS,
1004                            SocialActivityCounterConstants.TYPE_ACTOR, 1,
1005                            SocialActivityCounterConstants.PERIOD_LENGTH_SYSTEM);
1006            }
1007    
1008            protected boolean addActivityCounter(
1009                    User user, User assetEntryUser,
1010                    SocialActivityCounterDefinition activityCounterDefinition) {
1011    
1012                    if ((user.isDefaultUser() || !user.isActive()) &&
1013                            (activityCounterDefinition.getOwnerType() !=
1014                                    SocialActivityCounterConstants.TYPE_ASSET)) {
1015    
1016                            return false;
1017                    }
1018    
1019                    if ((assetEntryUser.isDefaultUser() || !assetEntryUser.isActive()) &&
1020                            (activityCounterDefinition.getOwnerType() !=
1021                                    SocialActivityCounterConstants.TYPE_ACTOR)) {
1022    
1023                            return false;
1024                    }
1025    
1026                    if (!activityCounterDefinition.isEnabled() ||
1027                            (activityCounterDefinition.getIncrement() == 0)) {
1028    
1029                            return false;
1030                    }
1031    
1032                    String name = activityCounterDefinition.getName();
1033    
1034                    if ((user.getUserId() == assetEntryUser.getUserId()) &&
1035                            (name.equals(SocialActivityCounterConstants.NAME_CONTRIBUTION) ||
1036                             name.equals(SocialActivityCounterConstants.NAME_POPULARITY))) {
1037    
1038                            return false;
1039                    }
1040    
1041                    return true;
1042            }
1043    
1044            protected void adjustUserContribution(AssetEntry assetEntry, boolean enable)
1045                    throws PortalException, SystemException {
1046    
1047                    if (assetEntry == null) {
1048                            return;
1049                    }
1050    
1051                    SocialActivityCounter latestPopularityActivityCounter =
1052                            fetchLatestActivityCounter(
1053                                    assetEntry.getGroupId(), assetEntry.getClassNameId(),
1054                                    assetEntry.getClassPK(),
1055                                    SocialActivityCounterConstants.NAME_POPULARITY,
1056                                    SocialActivityCounterConstants.TYPE_ASSET);
1057    
1058                    if ((latestPopularityActivityCounter == null) ||
1059                            (enable && latestPopularityActivityCounter.isActive()) ||
1060                            (!enable && !latestPopularityActivityCounter.isActive())) {
1061    
1062                            return;
1063                    }
1064    
1065                    int factor = -1;
1066    
1067                    if (enable) {
1068                            factor = 1;
1069                    }
1070    
1071                    SocialActivityCounter latestContributionActivityCounter =
1072                            fetchLatestActivityCounter(
1073                                    assetEntry.getGroupId(),
1074                                    PortalUtil.getClassNameId(User.class.getName()),
1075                                    assetEntry.getUserId(),
1076                                    SocialActivityCounterConstants.NAME_CONTRIBUTION,
1077                                    SocialActivityCounterConstants.TYPE_CREATOR);
1078    
1079                    if (latestContributionActivityCounter == null) {
1080                            return;
1081                    }
1082    
1083                    int startPeriod = SocialCounterPeriodUtil.getStartPeriod();
1084    
1085                    if (latestContributionActivityCounter.getStartPeriod() != startPeriod) {
1086                            latestContributionActivityCounter = addActivityCounter(
1087                                    latestContributionActivityCounter.getGroupId(),
1088                                    latestContributionActivityCounter.getClassNameId(),
1089                                    latestContributionActivityCounter.getClassPK(),
1090                                    latestContributionActivityCounter.getName(),
1091                                    latestContributionActivityCounter.getOwnerType(), 0,
1092                                    latestContributionActivityCounter.getTotalValue(),
1093                                    SocialCounterPeriodUtil.getStartPeriod(),
1094                                    SocialActivityCounterConstants.END_PERIOD_UNDEFINED,
1095                                    latestContributionActivityCounter.getActivityCounterId(),
1096                                    SocialActivityCounterConstants.PERIOD_LENGTH_SYSTEM);
1097                    }
1098    
1099                    if (latestPopularityActivityCounter.getStartPeriod() == startPeriod) {
1100                            latestContributionActivityCounter.setCurrentValue(
1101                                    latestContributionActivityCounter.getCurrentValue() +
1102                                            (latestPopularityActivityCounter.getCurrentValue() *
1103                                                    factor));
1104                    }
1105    
1106                    latestContributionActivityCounter.setTotalValue(
1107                            latestContributionActivityCounter.getTotalValue() +
1108                                    (latestPopularityActivityCounter.getTotalValue() * factor));
1109    
1110                    socialActivityCounterPersistence.update(
1111                            latestContributionActivityCounter);
1112            }
1113    
1114            protected boolean checkActivityLimit(
1115                            User user, SocialActivity activity,
1116                            SocialActivityCounterDefinition activityCounterDefinition)
1117                    throws PortalException, SystemException {
1118    
1119                    if (activityCounterDefinition.getLimitValue() == 0) {
1120                            return true;
1121                    }
1122    
1123                    long classPK = activity.getClassPK();
1124    
1125                    String name = activityCounterDefinition.getName();
1126    
1127                    if (name.equals(SocialActivityCounterConstants.NAME_PARTICIPATION)) {
1128                            classPK = 0;
1129                    }
1130    
1131                    SocialActivityLimit activityLimit =
1132                            socialActivityLimitPersistence.fetchByG_U_C_C_A_A(
1133                                    activity.getGroupId(), user.getUserId(),
1134                                    activity.getClassNameId(), classPK, activity.getType(),
1135                                    activityCounterDefinition.getName());
1136    
1137                    if (activityLimit == null) {
1138                            try {
1139                                    activityLimit =
1140                                            socialActivityLimitLocalService.addActivityLimit(
1141                                                    user.getUserId(), activity.getGroupId(),
1142                                                    activity.getClassNameId(), classPK, activity.getType(),
1143                                                    activityCounterDefinition.getName(),
1144                                                    activityCounterDefinition.getLimitPeriod());
1145                            }
1146                            catch (SystemException se) {
1147                                    activityLimit =
1148                                            socialActivityLimitPersistence.fetchByG_U_C_C_A_A(
1149                                                    activity.getGroupId(), user.getUserId(),
1150                                                    activity.getClassNameId(), classPK, activity.getType(),
1151                                                    activityCounterDefinition.getName());
1152    
1153                                    if (activityLimit == null) {
1154                                            throw se;
1155                                    }
1156                            }
1157                    }
1158    
1159                    int count = activityLimit.getCount(
1160                            activityCounterDefinition.getLimitPeriod());
1161    
1162                    if (count < activityCounterDefinition.getLimitValue()) {
1163                            activityLimit.setCount(
1164                                    activityCounterDefinition.getLimitPeriod(), count + 1);
1165    
1166                            socialActivityLimitPersistence.update(activityLimit);
1167    
1168                            return true;
1169                    }
1170    
1171                    return false;
1172            }
1173    
1174            protected void clearFinderCache() {
1175                    PortalCache<String, SocialActivityCounter> portalCache =
1176                            MultiVMPoolUtil.getCache(
1177                                    SocialActivityCounterFinder.class.getName());
1178    
1179                    portalCache.removeAll();
1180            }
1181    
1182            protected String getLockKey(
1183                    long groupId, long classNameId, long classPK, String name,
1184                    int ownerType) {
1185    
1186                    StringBundler sb = new StringBundler(7);
1187    
1188                    sb.append(StringUtil.toHexString(groupId));
1189                    sb.append(StringPool.POUND);
1190                    sb.append(StringUtil.toHexString(classNameId));
1191                    sb.append(StringPool.POUND);
1192                    sb.append(StringUtil.toHexString(classPK));
1193                    sb.append(StringPool.POUND);
1194                    sb.append(name);
1195    
1196                    return sb.toString();
1197            }
1198    
1199            protected void incrementActivityCounter(
1200                            long groupId, long classNameId, long classPK, String name,
1201                            int ownerType, int increment, int periodLength)
1202                    throws PortalException, SystemException {
1203    
1204                    SocialActivityCounter activityCounter = fetchLatestActivityCounter(
1205                            groupId, classNameId, classPK, name, ownerType);
1206    
1207                    if (activityCounter == null) {
1208                            activityCounter = addActivityCounter(
1209                                    groupId, classNameId, classPK, name, ownerType, 0, 0,
1210                                    SocialCounterPeriodUtil.getStartPeriod(),
1211                                    SocialActivityCounterConstants.END_PERIOD_UNDEFINED);
1212    
1213                            if (periodLength > 0) {
1214                                    activityCounter.setStartPeriod(
1215                                            SocialCounterPeriodUtil.getActivityDay());
1216                            }
1217                    }
1218    
1219                    if (!activityCounter.isActivePeriod(periodLength)) {
1220                            activityCounter = addActivityCounter(
1221                                    activityCounter.getGroupId(), activityCounter.getClassNameId(),
1222                                    activityCounter.getClassPK(), activityCounter.getName(),
1223                                    activityCounter.getOwnerType(), 0,
1224                                    activityCounter.getTotalValue(),
1225                                    SocialCounterPeriodUtil.getStartPeriod(),
1226                                    SocialActivityCounterConstants.END_PERIOD_UNDEFINED,
1227                                    activityCounter.getActivityCounterId(), periodLength);
1228                    }
1229    
1230                    activityCounter.setCurrentValue(
1231                            activityCounter.getCurrentValue() + increment);
1232                    activityCounter.setTotalValue(
1233                            activityCounter.getTotalValue() + increment);
1234    
1235                    socialActivityCounterPersistence.update(activityCounter);
1236            }
1237    
1238            protected void incrementActivityCounter(
1239                            long groupId, User user, AssetEntry assetEntry,
1240                            SocialActivityCounterDefinition activityCounterDefinition)
1241                    throws PortalException, SystemException {
1242    
1243                    int ownerType = activityCounterDefinition.getOwnerType();
1244                    long userClassNameId = PortalUtil.getClassNameId(User.class.getName());
1245    
1246                    if (ownerType == SocialActivityCounterConstants.TYPE_ACTOR) {
1247                            incrementActivityCounter(
1248                                    groupId, userClassNameId, user.getUserId(),
1249                                    activityCounterDefinition.getName(), ownerType,
1250                                    activityCounterDefinition.getIncrement(),
1251                                    activityCounterDefinition.getPeriodLength());
1252                    }
1253                    else if (ownerType == SocialActivityCounterConstants.TYPE_ASSET) {
1254                            incrementActivityCounter(
1255                                    groupId, assetEntry.getClassNameId(), assetEntry.getClassPK(),
1256                                    activityCounterDefinition.getName(), ownerType,
1257                                    activityCounterDefinition.getIncrement(),
1258                                    activityCounterDefinition.getPeriodLength());
1259                    }
1260                    else {
1261                            incrementActivityCounter(
1262                                    groupId, userClassNameId, assetEntry.getUserId(),
1263                                    activityCounterDefinition.getName(), ownerType,
1264                                    activityCounterDefinition.getIncrement(),
1265                                    activityCounterDefinition.getPeriodLength());
1266                    }
1267            }
1268    
1269            private static Log _log = LogFactoryUtil.getLog(
1270                    SocialActivityCounterLocalService.class);
1271    
1272    }