All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.cq.social.notifications.client.api.AbstractSocialNotificationCollection Maven / Gradle / Ivy

/*************************************************************************
 *
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2015 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 **************************************************************************/
package com.adobe.cq.social.notifications.client.api;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;

import org.apache.commons.lang.StringUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.SyntheticResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.cq.social.activitystreams.client.api.SocialActivity;
import com.adobe.cq.social.commons.listing.QueryFilterUtil;
import com.adobe.cq.social.commons.listing.QueryFilterUtil.QueryFilterException;
import com.adobe.cq.social.community.api.CommunityContext;
import com.adobe.cq.social.notifications.api.Notification;
import com.adobe.cq.social.notifications.api.NotificationConstants;
import com.adobe.cq.social.notifications.api.NotificationManager;
import com.adobe.cq.social.scf.ClientUtilities;
import com.adobe.cq.social.scf.CollectionPagination;
import com.adobe.cq.social.scf.PageInfo;
import com.adobe.cq.social.scf.QueryRequestInfo;
import com.adobe.cq.social.scf.SocialComponent;
import com.adobe.cq.social.scf.SocialComponentFactory;
import com.adobe.cq.social.scf.SocialComponentFactoryManager;
import com.adobe.cq.social.scf.core.BaseSocialComponent;
import com.adobe.cq.social.scf.core.CollectionSortedOrder;
import com.adobe.cq.social.ugc.api.ComparisonType;
import com.adobe.cq.social.ugc.api.Constraint;
import com.adobe.cq.social.ugc.api.ConstraintGroup;
import com.adobe.cq.social.ugc.api.Operator;
import com.adobe.cq.social.ugc.api.UgcFilter;
import com.adobe.cq.social.ugc.api.ValueConstraint;
import com.adobe.cq.social.ugcbase.SocialUtils;
import com.adobe.granite.activitystreams.ActivityException;

public abstract class AbstractSocialNotificationCollection extends BaseSocialComponent implements
    SocialNotificationCollection {
    private static final Logger LOG = LoggerFactory.getLogger(AbstractSocialNotificationCollection.class);

    private CollectionPagination pagination;
    private CollectionSortedOrder sortOrder = CollectionSortedOrder.DEFAULT_ORDER;
    private List notifications;
    private PageInfo pageInfo;

    private NotificationManager notificationManager;
    private String userId;
    private String channelId;
    private int maxNumberNotifications;
    private long numUnread = -1;
    private UgcFilter ugcFilter;
    private static String PROP_FILTER_NAME = "filter";

    public AbstractSocialNotificationCollection(final Resource resource, final ClientUtilities clientUtils,
        final NotificationManager notificationManager) {
        this(resource, clientUtils, QueryRequestInfo.DEFAULT_QUERY_INFO_FACTORY.create(), notificationManager);
    }

    public AbstractSocialNotificationCollection(final Resource resource, final ClientUtilities clientUtilities,
        final QueryRequestInfo queryRequestInfo, final NotificationManager notificationManager) {
        super(resource, clientUtilities);
        init(resource, clientUtilities, queryRequestInfo, notificationManager, getProperty(PROP_USER_ID, ""));
    }

    public AbstractSocialNotificationCollection(final Resource resource, final ClientUtilities clientUtilities,
        final QueryRequestInfo queryRequestInfo, final NotificationManager notificationManager, final String userId) {
        super(resource, clientUtilities);
        init(resource, clientUtilities, queryRequestInfo, notificationManager, userId);
    }

    @Override
    public long getUnreadCount() {
        if (numUnread == -1) {
            try {
                if (StringUtils.isBlank(userId)) {
                    throw new IllegalStateException("UserId has not been set.");
                }

                numUnread =
                    notificationManager.getUnreadCount(resource.getResourceResolver(), getPathConstraint(resource),
                        channelId);
            } catch (final RepositoryException e) {
                LOG.error("Failed to obtain number of unread count.", e);
            } catch (final ActivityException e) {
                // there is no stream, so return 0
                numUnread = 0;
            }
        }
        return numUnread;
    }

    @Override
    public int getTotalSize() {
        if (this.maxNumberNotifications < 0) {  // we use -1 as the default infinite scroll size
            return (int) pageInfo.getPageSize() * (int) pageInfo.getSelectedPage() + 2;
        } else {
            getItems();
            return notifications.size();
        }
    }

    @Override
    public void setPagination(final CollectionPagination pagination) {
        this.pagination = pagination;
    }

    @Override
    public void setSortedOrder(final CollectionSortedOrder sortedOrder) {
        this.sortOrder = sortedOrder;
    }

    @Override
    public List getItems() {
        final ResourceResolver resolver = resource.getResourceResolver();
        if (pagination.getSize() > 0 // don't want to fetch anything if user asks for 0 item
                && notifications == null // we already fetch once
                && notificationManager.isStreamExist(resolver) // no need to fetch if there is no stream for this
        // current user
        ) {

            Iterable notificationList = null;
            notificationList =
                notificationManager.getNotifications(resolver, ugcFilter, pagination.getOffset(),
                    pagination.getSize());
            if (notificationList != null) {
                notifications = new ArrayList();
                for (final Notification notification : notificationList) {
                    final Resource notificationResource =
                            new SyntheticResource(resolver, notification.getPath(), SocialNotification.RESOURCE_TYPE);
                    final SocialComponentFactoryManager factoryManager =
                            clientUtils.getSocialComponentFactoryManager();
                    final SocialComponentFactory componentFactory =
                            factoryManager.getSocialComponentFactory(notificationResource);
                    SocialComponent sc = null;
                    if (componentFactory instanceof SocialNotificationComponentFactory) {
                        sc =
                                ((SocialNotificationComponentFactory) componentFactory).getSocialComponent(
                                        notificationResource, clientUtils, notification);
                    } else {
                        sc = componentFactory.getSocialComponent(notificationResource, clientUtils, null);
                    }
                    if (sc != null) {
                        notifications.add(sc);
                    }
                }
                return notifications;
            }
        }
        // Return empty list if we failed to obtain the list
        if (notifications == null) {
            LOG.debug("Return empty list");
            return Collections.emptyList();
        } else {
            LOG.debug("Returning existing notifications list");
            return notifications;
        }
    }

    @Override
    public String getChannelId() {
        return this.channelId;
    }

    @Override
    public String getUserId() {
        return this.userId;
    }

    @Override
    public PageInfo getPageInfo() {
        return this.pageInfo;
    }

    public int getUnreadCountConfigured(){
        return notificationManager.getUnreadCountConfigured();
    }

    private void init(final Resource resource, final ClientUtilities clientUtilities,
        final QueryRequestInfo queryRequestInfo, final NotificationManager notificationManager, final String userId) {
        if (notificationManager == null) {
            throw new IllegalArgumentException("NoficationManager can not be null.");
        }
        this.notificationManager = notificationManager;
        if (StringUtils.isBlank(userId)) {
            throw new IllegalArgumentException("UserId can not be null.");
        }
        this.userId = userId;
        LOG.debug("userId: {}", userId);

        if (getProperties().containsKey(PROP_CHANNEL_ID)) {
            this.channelId = getProperty(PROP_CHANNEL_ID, SocialNotificationCollection.DEFAULT_CHANNEL);

            LOG.debug("channelId: {}", channelId);
        }
        if (StringUtils.isEmpty(channelId)) {
            channelId = SocialNotificationCollection.DEFAULT_CHANNEL;
        }
        this.pagination = queryRequestInfo.getPagination();
        if (pagination == null) {
            pagination = CollectionPagination.DEFAULT_PAGINATION;
        }
        pageInfo = new PageInfo(this, clientUtils, this.pagination, getQueryParams(queryRequestInfo));
        maxNumberNotifications = Integer.parseInt(getProperty(PROP_MAX_NOTIFICATIONS, DEFAULT_MAX_NOTIFICATIONS));
        try {
            ugcFilter = getFilter(queryRequestInfo, resource);
        } catch (final QueryFilterException e) {
            LOG.error("Failed to parse query predicates", e);
            ugcFilter = null;
        }

    }

    private UgcFilter getFilter(final QueryRequestInfo queryRequestInfo, final Resource resource)
        throws QueryFilterException {
        final UgcFilter ugcfilters = new UgcFilter();
        // only add the channel filter if it is not all channel id
        if (StringUtils.isNotBlank(channelId) && !SocialNotificationCollection.ALL_CHANNEL_ID.equals(channelId)) {
            final Constraint constraint =
                new ValueConstraint(NotificationConstants.NOTIFICATION_CHANNELS_PROP, channelId);
            ugcfilters.addConstraint(constraint);
        }

        ugcfilters.and(getPathConstraint(resource));

        final Map predicates = queryRequestInfo.getPredicates();
        if (predicates != null) {
            final String filters[] = predicates.get(PROP_FILTER_NAME);
            if (filters != null && filters.length > 0) {
                final List constraints = QueryFilterUtil.parseFilter(filters);
                if (constraints != null && !constraints.isEmpty()) {
                    for (final ConstraintGroup cg : constraints) {
                        ugcfilters.and(cg);
                    }
                }
            }
        }

        return ugcfilters;
    }

    public static Constraint getPathConstraint(final Resource resource) {
        final CommunityContext context = resource.adaptTo(CommunityContext.class);

        final ConstraintGroup cgTargetPaths = new ConstraintGroup();
        final String siteId = context.getSiteId();
        List pathConstraints = new ArrayList();
        if (StringUtils.isNotEmpty(siteId)) {
            if (StringUtils.isNotEmpty(context.getCommunityGroupId())) {
                pathConstraints.add(context.getCommunityGroupPath() + "/");
            } else {
                pathConstraints = context.getSiteContentPaths();
            }
        } else {
            pathConstraints.add(getApplicationPagePath(resource));
        }
        final SocialUtils socialUtils = resource.getResourceResolver().adaptTo((SocialUtils.class));
        final String asiPath = socialUtils.getSocialResourceProvider(resource).getASIPath();

        for (final String pathConstraint : pathConstraints) {
            if (StringUtils.isNotEmpty(pathConstraint)) {
                final String ending = pathConstraint.endsWith("/") ? "" : "/";
                final Constraint constraint =
                    new ValueConstraint(SocialActivity.TARGET_ID_PROP, pathConstraint + ending,
                        ComparisonType.BeginsWith);
                constraint.setOperator(Operator.Or);
                cgTargetPaths.addConstraint(constraint);
                final Constraint ugcconstraint =
                    new ValueConstraint(SocialActivity.TARGET_ID_PROP, asiPath + pathConstraint + ending,
                        ComparisonType.BeginsWith);
                ugcconstraint.setOperator(Operator.Or);
                cgTargetPaths.addConstraint(ugcconstraint);
            }
        }
        return cgTargetPaths;
    }

    private static String getApplicationPagePath(final Resource resource) {
        String path = resource.getPath();
        final Session session = resource.getResourceResolver().adaptTo(Session.class);

        String pagePath = null;
        if (path.startsWith("/content")) { // only looking for page if the path is under /content
            try {
                for (; path != null;) {
                    final String parentPath = ResourceUtil.getParent(path);
                    if (parentPath != null) {
                        final Node parentNode = session.getNode(parentPath);
                        if (parentNode.isNodeType("cq:Page")) {
                            pagePath = parentNode.getPath();
                        }
                    }
                    path = parentPath;
                }
            } catch (final RepositoryException e) {
                LOG.error("Failed to obtain application page path for " + path, e);
            }
        }
        return pagePath;
    }

    private String getQueryParams(final QueryRequestInfo queryInfo) {
        final Map params = queryInfo.getPredicates();
        final StringBuilder sb = new StringBuilder();
        boolean firstParam = true;
        try {
            for (final Entry ks : params.entrySet()) {
                final String key = ks.getKey();
                final String[] value = ks.getValue();
                for (int i = 0; i < value.length; i++) {
                    if (!firstParam) {
                        sb.append("&");
                    } else {
                        firstParam = false;
                    }
                    sb.append(URLEncoder.encode(key, "UTF-8")).append("=")
                        .append(URLEncoder.encode(value[i], "UTF-8"));
                }
            }
            final String retVal = sb.toString();
            return (retVal.isEmpty()) ? null : retVal;
        } catch (final UnsupportedEncodingException e) {
            LOG.error("Error encoding params.  " + sb.toString());
            return null;
        }
    }
}