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

com.adobe.cq.social.commons.comments.api.AbstractCommentCollection Maven / Gradle / Ivy

/*************************************************************************
 *
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2013 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.commons.comments.api;

import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.jcr.Session;

import com.adobe.cq.social.commons.CommentException;
import com.adobe.cq.social.scf.core.BaseQueryRequestInfo;
import com.adobe.cq.social.srp.SocialResourceProvider;
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.ValueMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.cq.social.SocialException;
import com.adobe.cq.social.commons.CommentSystem;
import com.adobe.cq.social.commons.comments.listing.CommentSocialComponentList;
import com.adobe.cq.social.commons.comments.listing.CommentSocialComponentListProvider;
import com.adobe.cq.social.commons.comments.listing.CommentSocialComponentListProviderManager;
import com.adobe.cq.social.scf.ClientUtilities;
import com.adobe.cq.social.scf.CollectionPagination;
import com.adobe.cq.social.scf.JsonException;
import com.adobe.cq.social.scf.QueryRequestInfo;
import com.adobe.cq.social.scf.core.BaseSocialComponent;
import com.adobe.cq.social.scf.core.CollectionSortedOrder;
import com.adobe.cq.social.scf.core.ResourceID;
import com.adobe.cq.social.translation.TranslationSCFUtil;
import com.adobe.cq.social.ugcbase.SocialUtils;
import com.day.cq.wcm.api.Page;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

/**
 * Base SocialComponentCollection class which maintains a list of {@link Comment}.
 * @param  any SocialComponent type that extends the {@link Comment} data type.
 */
public abstract class AbstractCommentCollection extends
    BaseSocialComponent implements CommentCollection {
    private static final Logger LOG = LoggerFactory.getLogger(AbstractCommentCollection.class);
    private CommentSocialComponentListProviderManager commentListProviderManager;
    private Calendar lastModified;
    private Calendar created;
    private CommentSocialComponentList comments;
    private QueryRequestInfo queryInfo;
    private PageInfo pageInfo;
    private CommentSystem cs;
    private String friendlyUrl;
    private T configuration;
    private boolean mayPost = false;
    private boolean needComments = true;
    private boolean needCommentSystem = true;
    private String filterFromRequest = null;
    private final String FILTER = "filter";
    private final String PAGE = "page has";

    /**
     * Constructor using the specified {@link ResourceID} which should be the root of the collection without paging
     * specification. Paging information is not created until the pagination is specified.
     * @param id the resource id of the CommentCollection
     * @param resolver the resource resolver used to obtain the resource in the JCR.
     * @param clientUtils the clientUtils
     * @param commentListProviderManager CommentSocialComponentListProviderManager
     * @see AbstractCommentCollection#setPagination(CollectionPagination)
     */
    public AbstractCommentCollection(final ResourceID id, final ResourceResolver resolver,
        final ClientUtilities clientUtils, final CommentSocialComponentListProviderManager commentListProviderManager) {
        this(resolver.resolve(id.getResourceIdentifier()), clientUtils, commentListProviderManager);
    }

    /**
     * Constructor using the specified {@link Resource} which should be the root of the collection without paging
     * specification. Paging information is not created until the pagination is specified.
     * @param resource the resource.
     * @param clientUtils the clientUtils
     * @param commentListProviderManager  CommentSocialComponentListProviderManager
     * @see AbstractCommentCollection#setPagination(CollectionPagination)
     */
    public AbstractCommentCollection(final Resource resource, final ClientUtilities clientUtils,
        final CommentSocialComponentListProviderManager commentListProviderManager) {
        super(resource, clientUtils);
        /** Logger for this class. */
        init(resource, clientUtils, QueryRequestInfo.DEFAULT_QUERY_INFO_FACTORY.create(), false,
            commentListProviderManager);
    }

    /**
     * Construct a {@link CommentCollection} using the specified {@link Resource} which should be the root of the
     * collection.
     * @param resource the resource where the CommentCollection is located
     * @param clientUtils the clientUtilities instance
     * @param commentListProviderManager CommentSocialComponentListProviderManager
     * @param queryInfo  QueryRequestInfo
     */
    public AbstractCommentCollection(final Resource resource, final ClientUtilities clientUtils,
        final QueryRequestInfo queryInfo, final CommentSocialComponentListProviderManager commentListProviderManager) {
        super(resource, clientUtils);
        init(resource, clientUtils, queryInfo, true, commentListProviderManager);
    }

    /**
     * Constructor initialization helper method.
     * @param resource Resource
     * @param clientUtils ClientUtilities
     * @param queryInfo QueryRequestInfo
     * @param buildPages boolean
     * @param commentListProviderManager CommentSocialComponentListProviderManager
     * @throws SocialException SocialException
     */
    private void init(final Resource resource, final ClientUtilities clientUtils, final QueryRequestInfo queryInfo,
        final boolean buildPages, final CommentSocialComponentListProviderManager commentListProviderManager)
        throws SocialException {
        this.queryInfo = queryInfo;
        this.setCommentListProviderManager(commentListProviderManager);

    }

    private void initCommentSystem() {
        if (!needCommentSystem) {
            return;
        }
        needCommentSystem = false;
        // If we don't try and get the UGC resource before the adapt below we can generate things with the wrong
        // sling resource type, so while we drop this value on the floor it is necessary to make this call here.
        // This is because if we have a TypeOverridingResourceWrapper it will get unwrapped when the adaptTo is
        // called on it causing it to lose the type information, so in this case we can pre-create the ugc ACL
        // path and "fix" it. This is nearly a NOOP when there is data and will happen anyway in the adaptTo
        // later if there isn't data so we're not paying the create price twice, just 1 extra read of JCR.
        final Resource ugcResource =
            clientUtils.getSocialUtils().getUGCResource(resource, resource.getResourceType());
        // TODO: Should we get these values directly from the resource properties?
        cs = resource.adaptTo(CommentSystem.class);
        setLastModified(cs.getLastModified());
        setCreated(cs.getCreated());
        configuration = createConfiguration(cs.getResource());

    }

    private void initComments() {
        if (!needComments) {
            return;
        }
        needComments = false;
        //This is needed for deeplinking of repliess
        if (queryInfo.getPredicates().containsKey(FILTER) && (queryInfo.getPredicates().get(FILTER)[0].startsWith(PAGE))) {
            filterFromRequest = queryInfo.getPredicates().get(FILTER)[0].replace(PAGE, "").trim();
            this.queryInfo = new BaseQueryRequestInfo(false, new HashMap(), queryInfo.getPagination(), queryInfo.getSortOrder(), queryInfo.getSortBy());
        }

        final QueryRequestInfo commentsQueryInfo = QueryRequestInfo.DEFAULT_QUERY_INFO_FACTORY.create(queryInfo);
        commentsQueryInfo.setSortOrder(getConfiguration().getSortOrder());
        final List> sortFields = commentsQueryInfo.getSortFields();

        CollectionPagination currentPagination = commentsQueryInfo.getPagination();;
        int offset = currentPagination.getOffset();

        if (filterFromRequest != null) {
            int replyIndex = getChildIndex(filterFromRequest);
            int pageNo = replyIndex/this.configuration.getPageSize() ;
            offset = pageNo * this.configuration.getPageSize();
            commentsQueryInfo.setSortOrder(CollectionSortedOrder.DEFAULT_ORDER);
        } else {
            if (sortFields == null || sortFields.isEmpty()) {
                commentsQueryInfo.setSortFields(getConfiguration().getSortFields());
            }
        }

        int pageSize = this.configuration.getPageSize();
        if (queryInfo != null) {
            pageSize =
                queryInfo.getPagination() == CollectionPagination.DEFAULT_PAGINATION ? this.configuration
                    .getPageSize() : currentPagination.getSignedSize();
        }
        commentsQueryInfo
            .setPagination(new CollectionPagination(offset, pageSize, currentPagination
                .getEmbedLevel() + 1, currentPagination.getSortIndex(), this.configuration.getPageSize()));

        final CommentSocialComponentListProvider listProvider =
            this.getCommentListProviderManager().getCommentSocialComponentListProvider(resource, commentsQueryInfo);
        if (listProvider == null) {
            throw new SocialException(String.format(
                "Could not find a SCF list provider for %1$s whose resource type is: %2$s.", resource.getPath(),
                resource.getResourceType()));
        }
        if (TranslationSCFUtil.isSmartRenderingOn(resource, clientUtils)) {
            commentsQueryInfo.setTranslationRequest(true);
        }
        comments = listProvider.getCommentSocialComponentList(this, commentsQueryInfo, clientUtils);
        if (!queryInfo.isQuery()) {
            pageInfo = new PageInfo(this, clientUtils, commentsQueryInfo.getPagination());
        } else {
            pageInfo = new PageInfo(this, clientUtils, commentsQueryInfo.getPagination(), queryInfo.getQueryString());
        }
    }

    /* Returns the index of a reply within all replies of a given comment for given conditions */
    private int getChildIndex(final String commentToCheck) throws CommentException {
        int replyIndex = 0;
        if (clientUtils != null) {
            final SocialResourceProvider srp = resource.adaptTo(SocialResourceProvider.class);
            if (srp != null) {
                final String pathToCheck = resource.getPath();
                replyIndex = srp.getCommentIndex(pathToCheck, com.adobe.cq.social.commons.Comment.RESOURCE_TYPE, resource.getResourceResolver(),
                        srp.getASIPath() + pathToCheck + "/" + commentToCheck.split("/")[0], true);
            }
        }
        if (replyIndex < 0) {
            LOG.warn("Unable to get comment index. Fail to provide direct link to comment");
            replyIndex = 0; //let's open 1st page
        }
        return replyIndex;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean getMayPost() {
        initCommentSystem();
        final SocialUtils socialUtils = clientUtils.getSocialUtils();
        final Session session = resource.getResourceResolver().adaptTo(Session.class);
        final String aclPath = socialUtils.resourceToACLPath(cs.getResource());
        if (!clientUtils.userIsAnonymous()) {
            mayPost =
                clientUtils.getSocialUtils().canAddNode(session, aclPath == null ? CommentSystem.PATH_UGC : aclPath);
        }

        return mayPost;
    }

    /**
     * Set the collection list range. If this is not set, then the return list will start at offset 0 and the default
     * size.
     * @param pagination pagination configuration
     */
    @Override
    public void setPagination(final CollectionPagination pagination) {
        initComments();
        queryInfo.setPagination(pagination);
        comments.setPagination(pagination);
        pageInfo = new PageInfo(this, clientUtils, pagination);
    }

    /**
     * Get the pagination settings.
     * @return the pagination settings.
     */
    protected CollectionPagination getPagination() {
        return queryInfo.getPagination();
    }

    /**
     * Get the query info
     * @return QueryRequestInfo QueryRequestInfo
     */
    protected QueryRequestInfo getQueryRequestInfo() {
        return queryInfo;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Calendar getCreated() {
        initCommentSystem();
        return created;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Calendar getLastModified() {
        initCommentSystem();
        return lastModified;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getTotalSize() {
        initComments();
        return comments.getTotalSize();
    }

    /**
     * Get the comments of this {@link CommentCollection} base on the pagination configuration.
     * @return List - a list of Comment
     */
    @Override
    public List getItems() {
        initComments();
        return comments; // comments.getComments();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setSortedOrder(final CollectionSortedOrder sortedOrder) {
        initComments();
        comments.setSortedOrder(sortedOrder);
    }

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

    @Override
    @JsonIgnoreProperties
    public boolean isTaggingAllowed() {
        return Boolean.parseBoolean(getProperty(CommentCollectionConfiguration.PN_ALLOW_TAGGING, "false"));
    }

    protected void setLastModified(final Calendar lastModified) {
        this.lastModified = lastModified;
    }

    protected void setCreated(final Calendar created) {
        this.created = created;
    }

    protected CommentSocialComponentListProviderManager getCommentListProviderManager() {
        return commentListProviderManager;
    }

    protected void setCommentListProviderManager(
        final CommentSocialComponentListProviderManager commentListProviderManager) {
        this.commentListProviderManager = commentListProviderManager;
    }

    @Override
    public T getConfiguration() {
        initCommentSystem();
        return configuration;
    }

    protected T createConfiguration(final Resource resource) {
        initCommentSystem();
        if (ResourceUtil.isNonExistingResource(cs.getResource()) && clientUtils != null) {
            final ValueMap vm = this.clientUtils.getDesignProperties(resource, CommentSystem.PROP_RESOURCE_TYPE);
            return (T) new AbstractCommentCollectionConfiguration(vm);
        }
        return (T) new AbstractCommentCollectionConfiguration(resource);
    }

    @Override
    public String getFriendlyUrl() {
        if (friendlyUrl != null) {
            return friendlyUrl;
        }
        final SocialUtils socialUtils = clientUtils.getSocialUtils();
        if (socialUtils != null) {
            final Page page = socialUtils.getContainingPage(resource);
            if (page != null) {
                friendlyUrl = clientUtils.externalLink(page.getPath(), false) + ".html";
            }
        }
        return friendlyUrl;
    }

    @Override
    public Map getAsMap() {
        initCommentsWithPrefetch();
        return super.getAsMap();
    }

    @Override
    public String toJSONString(final boolean tidy) throws JsonException {
        initCommentsWithPrefetch();
        return super.toJSONString(tidy);
    }

    private void initCommentsWithPrefetch() {
        initComments();
        if (comments instanceof AbstractCommentCollectionPrefetch) {
            ((AbstractCommentCollectionPrefetch) comments).prefetchForGetAsMap();
        }
    }

}