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

com.adobe.cq.social.group.client.endpoints.AbstractCommunityGroupOperationService 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.group.client.endpoints;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.activation.DataSource;
import javax.annotation.Nullable;
import javax.jcr.Binary;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.servlet.http.HttpServletResponse;

import com.adobe.cq.social.commons.FunctionValidationUtil;
import com.adobe.cq.social.commons.exception.FunctionValidationException;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.api.JackrabbitSession;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.commons.JcrUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.JSONObject;
import org.apache.sling.commons.osgi.OsgiUtil;
import org.apache.sling.jcr.api.SlingRepository;
import org.apache.sling.jcr.resource.JcrResourceConstants;
import org.apache.sling.settings.SlingSettingsService;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.cq.social.blueprint.api.SiteActivationService;
import com.adobe.cq.social.blueprint.api.SiteBlueprintConstants;
import com.adobe.cq.social.blueprint.api.TemplateRolloutService;
import com.adobe.cq.social.commons.CollabUtil;
import com.adobe.cq.social.commons.CommentSystem;
import com.adobe.cq.social.community.api.CommunityConstants;
import com.adobe.cq.social.community.api.CommunityContext;
import com.adobe.cq.social.community.api.CommunityUserGroup;
import com.adobe.cq.social.communityfunctions.api.CommunityFunction;
import com.adobe.cq.social.console.utils.api.FunctionDefinitionUtils;
import com.adobe.cq.social.console.utils.api.internal.PropertiesUtils;
import com.adobe.cq.social.group.api.GroupConstants;
import com.adobe.cq.social.group.api.GroupException;
import com.adobe.cq.social.group.api.GroupService;
import com.adobe.cq.social.group.api.GroupUtil;
import com.adobe.cq.social.group.bundleactivator.impl.Activator;
import com.adobe.cq.social.group.client.api.CommunityGroup;
import com.adobe.cq.social.group.client.api.CommunityGroupCollection;
import com.adobe.cq.social.group.client.api.CommunityGroupConstants;
import com.adobe.cq.social.group.client.api.CommunityGroupFolder;
import com.adobe.cq.social.group.client.api.CommunityGroupService;
import com.adobe.cq.social.scf.ClientUtilities;
import com.adobe.cq.social.scf.Operation;
import com.adobe.cq.social.scf.OperationException;
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.serviceusers.internal.ServiceUserWrapper;
import com.adobe.cq.social.site.api.CommunitySiteConstants;
import com.adobe.cq.social.site.api.CommunitySiteService;
import com.adobe.cq.social.site.endpoints.AbstractPublishOperationService;
import com.adobe.cq.social.site.endpoints.SiteOperationUtils;
import com.adobe.cq.social.ugcbase.AsyncReverseReplicator;
import com.adobe.cq.social.ugcbase.SocialUtils;
import com.day.cq.commons.LanguageUtil;
import com.adobe.granite.confmgr.Conf;
import com.day.cq.commons.jcr.JcrUtil;
import com.day.cq.replication.ReplicationActionType;
import com.day.cq.wcm.api.NameConstants;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import com.day.cq.wcm.api.WCMException;
import com.day.text.Text;

/**
 * Provides abstract implementation of community group operations. This class can be extended to implement create
 * operations for any component that extends the community group.
 * @param  is a {@link CommunityGroupOperationExtension} that will be used as hooks by the extending class.
 * @param  is a {@link Operation} that is being provided by the extending class.
 */
@Component(metatype = false, componentAbstract = true)
public abstract class AbstractCommunityGroupOperationService
    extends AbstractPublishOperationService implements CommunityGroupOperations {

    private static final String MSM_SERVICE = "msm-service";

    private static final Logger LOG = LoggerFactory.getLogger(AbstractCommunityGroupOperationService.class);

    private static final String PROPERTY_IMAGE_NAME = "image";
    private static final String PROPERTY_BANNER_IMAGE_NAME = "pagebanner";
    private static final int PARAM_NAME_INDEX = 0;
    private static final int PARAM_CLASS_INDEX = 1;
    private static final int PARAM_REQUIRED_INDEX = 2;
    private static final Object requestParams[][] =
        {{CommunityGroupConstants.PROP_COMMUNITY_GROUP_DESCRIPTION, String.class, Boolean.FALSE},
            {CommunityGroupConstants.PROP_COMMUNITY_GROUP_NAME, String.class, Boolean.TRUE},
            {CommunityGroupConstants.PROP_COMMUNITY_GROUP_TITLE, String.class, Boolean.FALSE},
            {CommunityGroupConstants.PROP_COMMUNITY_GROUP_TYPE, String.class, Boolean.FALSE},
            {CommunityGroupConstants.PROP_COMMUNITY_GROUP_INVITE, String[].class, Boolean.FALSE},
            {CommunityGroupConstants.PROP_COMMUNITY_GROUP_INCLUDE_SITE_MODERATORS, Boolean.class, Boolean.FALSE},
            {CommunityGroupConstants.PROP_COMMUNITY_GROUP_MODERATOR, String[].class, Boolean.FALSE},
            {CommunityGroupConstants.PROP_COMMUNITY_GROUP_ADMINS, String[].class, Boolean.FALSE},
            {CommunityGroupConstants.PROP_COMMUNITY_INITIALLANGUAGES, String[].class, Boolean.FALSE},
            {CommunityGroupConstants.PROP_COMMUNITY_COMPLETE_TABLE_DATA, String[].class, Boolean.FALSE},
            {CommunitySiteConstants.PROP_FUNCTIONS, String.class, Boolean.FALSE},
            {CommunitySiteConstants.PROP_THEME_ID, String.class, Boolean.FALSE},
            {CommunityGroupConstants.PROP_COMMUNITY_GROUP_BLUEPRINT_ID, String.class, Boolean.TRUE}};
    private static final String specialParams[] =
        {CommunityGroupConstants.PROP_COMMUNITY_GROUP_FILE, CommunityGroupConstants.REQUEST_ATTACHMENT_PAGEBANNER};
    // list of parameters that are excluded from update
    private static final String unchangeableParams[] = {CommunityGroupConstants.PROP_COMMUNITY_GROUP_NAME,
        CommunityGroupConstants.PROP_COMMUNITY_GROUP_FILE, CommunityGroupConstants.REQUEST_ATTACHMENT_PAGEBANNER};

    private static final int ATTACHMENT_FILE_LIMIT = Integer.MAX_VALUE;
    private static final List WHITE_LIST = new ArrayList();
    private static final String[] BLACK_LIST = new String[0];

    private static final String USER_ADMIN = "communities-user-admin";
    // Reuse old paths for back comp
    private static final String DEFAULT_GROUP_ROOTTEMPLATE_ROOT = "/etc/community/templates/groups";
    private static final String DEFAULT_GROUP_TEMPLATE_ROOT =
        DEFAULT_GROUP_ROOTTEMPLATE_ROOT + CommunityConstants.REFERENCE_SUBPATH;
    private static final String CUSTOM_GROUP_TEMPLATE_ROOT =
        DEFAULT_GROUP_ROOTTEMPLATE_ROOT + CommunityConstants.CUSTOM_SUBPATH;

    private static final String DEFAULT_GROUP_ROOTTEMPLATE_RELATIVIEPATH_ROOT = "community/templates/groups";
    private static final String CUSTOM_GROUP_TEMPLATE_ROOT_CONF =
        "/conf/global/settings/" + DEFAULT_GROUP_ROOTTEMPLATE_RELATIVIEPATH_ROOT;
    private static final String CUSTOM_GROUP_TEMPLATE_ROOT_LIBS =
        "/libs/settings/" + DEFAULT_GROUP_ROOTTEMPLATE_RELATIVIEPATH_ROOT;
    private static final String PATH = "path";
    private static final String NAME = "name";
    private static final String NODE_PROPERTY_COMMENTATTACHMENT = "cq:CommentAttachment";

    protected ClientUtilities clientUtils;

    /**
     * The name of the property that holds a white list of form field names added as additional properties to the
     * comment.
     */
    public static final String PROPERTY_FIELD_WHITELIST = "fieldWhitelist";

    // delimiters to split the invited id list
    private final String delimiters = "[\\s,;]";

    /** Social Component Factory Manager. */
    @Reference
    private SocialComponentFactoryManager componentFactoryManager;

    @Reference
    private AsyncReverseReplicator replicator;

    @Reference
    private GroupService groupService;

    @Reference
    private CommunityGroupService communityGroupService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY, policy = ReferencePolicy.STATIC)
    private SlingRepository repository;

    @Reference
    private ServiceUserWrapper serviceUserWrapper;

    @Reference
    private ResourceResolverFactory resourceResolverFactory;

    @Reference
    protected SlingSettingsService settingsService;

    @Reference
    CommunitySiteService siteService;

    @Reference
    SiteActivationService activationService;

    @Reference
    private SocialUtils socialUtils;

    @Reference
    private FunctionDefinitionUtils funcDefUtils;

    @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC)
    private TemplateRolloutService siteBlueprintService;

    /**
     * White list field.
     */
    protected String[] fieldWhitelist;

    private void cleanupFailure(final Session session) {
        try {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Failure cleanup invoked.", new Throwable());
            }
            session.refresh(false);
        } catch (final RepositoryException e) {
            LOG.info("Failed to refresh the session", e);
        }

    }

    @Override
    protected ResourceResolverFactory getBundleLocalResourceResolverFactory() {
        if (resourceResolverFactory == null) {
            resourceResolverFactory = Activator.getService(ResourceResolverFactory.class);
        }
        return resourceResolverFactory;
    }

    @Override
    protected ServiceUserWrapper getServiceUserWrapper() {
        if (serviceUserWrapper == null) {
            serviceUserWrapper = Activator.getService(ServiceUserWrapper.class);
        }
        return serviceUserWrapper;
    }

    @Override
    public Resource create(final SlingHttpServletRequest request) throws OperationException {
        final Resource resource = request.getResource();
        final ResourceResolver resolver = resource.getResourceResolver();
        final Session session = resource.getResourceResolver().adaptTo(Session.class);

        // Validate group root
        boolean validPath = false;
        CommunityGroupCollection groupCollection = null;
        ArrayList returnresourceList = new ArrayList();
        // post to groupcollection node
        if (resource.isResourceType(CommunityGroupCollection.RESOURCE_TYPE)) {
            validPath = true;
            groupCollection = getCommunityGroupCollectionForResource(resource, request);
        } else {
            final PageManager pm = resolver.adaptTo(PageManager.class);
            final Page page = pm.getPage(resource.getPath());
            if (page != null) {
                // post to a group collection page
                Resource r =
                    resolver.getResource(resource.getPath() + "/" + CommunityGroupFolder.FOLDER_CONFIG_NODE_NAME);
                if (r != null) {
                    validPath = true;
                    groupCollection = getCommunityGroupCollectionForResource(resource, request);
                } else {
                    // post to a site page or a group page, currently we don't support this case
                    r = resolver.getResource(resource.getPath() + "/" + CommunityGroupConstants.CONFIG_NODE_NAME);
                    if (r != null) {
                        validPath = true;
                    }
                }
            }
        }
        if (!validPath) {
            throw new OperationException("Invalid Group Path:" + resource.getPath(),
                HttpServletResponse.SC_BAD_REQUEST);
        }
        if (groupCollection != null && !groupCollection.isCanAdd()) {
            throw new OperationException("No permission to create a community group.",
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
        if (!this.isPublishMode() && !siteService.mayPost(request, session)) {
            throw new OperationException("Access violation.", HttpServletResponse.SC_FORBIDDEN);
        }

        // Get the other definition/configuration properties of the site
        final Map props = new HashMap();
        try {
            getDefaultProperties(request, props, true);
            getCustomProperties(request, props, session);
        } catch (final RepositoryException e) {
            throw new OperationException("Failed to obtain community group parameters.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
        final U createOperation = getCreateOperation();
        getExtensionParameters(createOperation, request, props);

        // add creator to the invite list
        String invites = (String) props.get(CommunityGroupConstants.PROP_COMMUNITY_GROUP_INVITE);
        props.put(CommunityGroupConstants.PROP_COMMUNITY_GROUP_INVITE, invites);
        props.put(CommunityGroupConstants.PROP_COMMUNITY_GROUP_CREATOR, session.getUserID());

        // Get the name of the new group
        String name = null;
        name = (String) props.get(CommunityGroupConstants.PROP_COMMUNITY_GROUP_NAME);
        final PageManager pm = resolver.adaptTo(PageManager.class);
        final Page groupPage = pm.getContainingPage(resource);
        String root = groupPage.getPath();
        Resource groupPageResource = resolver.getResource(root);
        Resource firstGroupPageResource = groupPageResource;
        if (!GroupUtil.validateGroupName(resolver, name, root)) {
            throw new OperationException("Community group name is badly formatted " + name,
                HttpServletResponse.SC_BAD_REQUEST);
        }

        // get the attachment images
        final List attachments =
            getAttachmentsFromRequest(request, CommunityGroupConstants.PROP_COMMUNITY_GROUP_FILE);
        // get the banner images
        final List banner =
            getAttachmentsFromRequest(request, CommunityGroupConstants.REQUEST_ATTACHMENT_PAGEBANNER);
        // get the css file
        final List cssFiles =
            getAttachmentsFromRequest(request, CommunitySiteConstants.REQUEST_ATTACHMENT_PAGECSS);
        // create the community group
        // need to open a userAdminSession, since we allow every login user to create groups
        ResourceResolver userAdminResolver = null;
        try {
            userAdminResolver =
                getServiceUserWrapper().getServiceResourceResolver(getBundleLocalResourceResolverFactory(),
                    Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object) USER_ADMIN));
            returnresourceList.add(create(groupPageResource, name, props, attachments, banner, cssFiles,
                userAdminResolver.adaptTo(Session.class)));
            
            if (this.isPublishMode()) { // we don't need to create locale based groups on publish
                return groupPageResource;
            }
            String initialLanguage = (String) props.get(CommunityGroupConstants.PROP_COMMUNITY_INITIALLANGUAGES);
            if(initialLanguage != null){
            String[] initialLangLen = initialLanguage.split(",");
            String pathmk = null;
            LanguageUtil languageCode = new LanguageUtil();
            int i = 0;
            for (i = 0; i < initialLangLen.length; i++) {
                String languageCodePath = languageCode.getLanguageRoot(root);
                int codelanPath = languageCodePath.length();
                int rootlanPath = root.length();
                pathmk = root.substring(codelanPath, rootlanPath);
                String currentLanguage = initialLangLen[i];
                String relativeparent = Text.getRelativeParent(languageCodePath, 1);
                root = relativeparent.concat("/" + currentLanguage + pathmk);
                groupPageResource = resolver.getResource(root);

                // check if a group with the given name already exists for multi language group creation process
                final Page group = pm.getPage(root + "/" + name);
                if (group != null) {
                    LOG.info("Group {} already exists in {}", name, root);
                    continue;
                }
                
                if (!GroupUtil.validateGroupName(resolver, name, root)) {
                    throw new OperationException("Community group name is badly formatted " + name,
                        HttpServletResponse.SC_BAD_REQUEST);
                }
                if(groupPageResource!=null){
                    userAdminResolver =
                    getServiceUserWrapper().getServiceResourceResolver(getBundleLocalResourceResolverFactory(),
                        Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object) USER_ADMIN));
                    returnresourceList.add(create(groupPageResource, name, props, attachments, banner, cssFiles,
                    userAdminResolver.adaptTo(Session.class)));
                    userAdminResolver.close();
                }
             }
            }
            return firstGroupPageResource;
        } catch (final LoginException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to join community group.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } finally {
            if (userAdminResolver != null && userAdminResolver.isLive()) {
                userAdminResolver.close();
            }
        }
    }

    // TODO : create a hash map from next time
    protected Resource create(final Resource root, final String name, final Map properties,
        final List attachments, final Session session) throws OperationException {
        return create(root, name, properties, attachments, Collections.emptyList(),
            Collections.emptyList(), session);
    }

    protected Resource create(final Resource root, final String name, final Map properties,
        final List attachments, final List banner, final Session session)
        throws OperationException {
        return create(root, name, properties, attachments, banner, Collections.emptyList(), session);
    }

    protected Resource create(final Resource root, final String name, final Map properties,
        final List attachments, final List banner, final List cssFiles,
        final Session session) throws OperationException {
        try {
            final Map authInfo = new HashMap();
            authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, session);
            final ResourceResolver resolver = resourceResolverFactory.getResourceResolver(authInfo);
            final U createOperation = getCreateOperation();
            performBeforeActions(createOperation, session, root, properties);

            final String groupPath = root.getPath() + "/" + name;

            final CommunityContext context = root.adaptTo(CommunityContext.class);

            String nuggetRoot = context != null ? context.getSitePayloadPath() : "/var/community/publish";
            nuggetRoot = StringUtils.replace(nuggetRoot, CommunityGroupConstants.ROOT_FOR_PUBLISH_COMMUNITY_NUGGETS,
                CommunityGroupConstants.ROOT_FOR_CREATE_COMMUNITY_NUGGETS);
            final String nuggetPath = nuggetRoot + groupPath;
            final Node folder = JcrUtil.createPath(nuggetPath, "sling:Folder", session);
            final Node nugget =
                folder.addNode(SiteActivationService.REPLICATE_NODE_NAME, SiteActivationService.REPLICATE_NODE_TYPE);
            nugget.setProperty(SiteActivationService.REPLICATE_PROPERTY_PATH, root.getPath());
            nugget.setProperty(SiteActivationService.REPLICATE_PROPERTY_ACTION,
                CommunityGroupConstants.ACTION_TYPE_CREATE_COMMUNITY_GROUP);
            nugget.setProperty(GroupConstants.PROPERTY_FORM_PAYLOAD, nuggetPath);

            for (final Entry property : properties.entrySet()) {
                final String key = property.getKey();
                if (!isSpecialRequestParam(key)) {
                    JcrUtil.setProperty(nugget, key, property.getValue());
                }
            }
            // add image node if one has been uploaded
            if (attachments != null && !attachments.isEmpty()) {
                final DataSource ds = attachments.get(0);
                addImage(nuggetPath, resolver, ds.getInputStream(), ds.getContentType(), PROPERTY_IMAGE_NAME,
                    ds.getName());
            } else {
                // add default image if no image has been uploaded
                ResourceResolver serviceResolver = null;
                try {
                    serviceResolver =
                        getServiceUserWrapper().getServiceResourceResolver(getBundleLocalResourceResolverFactory(),
                            Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object) MSM_SERVICE));
                } catch (final LoginException e) {
                    LOG.error("Unable to add default group image", e);
                }

                if (serviceResolver != null) {
                    final Resource image =
                        serviceResolver.getResource(CommunityGroupConstants.DEFAULT_STREAMABLE_IMAGE_PATH);
                    if (image != null) {
                        addImage(nuggetPath, resolver, image.adaptTo(InputStream.class),
                            CommunityGroupConstants.DEFAULT_IMAGE_CONTENT_TYPE, PROPERTY_IMAGE_NAME,
                            CommunityGroupConstants.DEFAULT_IMAGE_NAME);
                    } else {
                        LOG.error("Unable to find default group image");
                    }
                    serviceResolver.close();
                }
            }
            // add banner image node if one has been uploaded
            final boolean hasBanner = banner != null && !banner.isEmpty();
            if (hasBanner) {
                final DataSource ds = banner.get(0);
                addImage(nuggetPath, resolver, ds.getInputStream(), ds.getContentType(), PROPERTY_BANNER_IMAGE_NAME,
                    ds.getName());
            }

            final boolean hasCSS = cssFiles != null && !cssFiles.isEmpty();
            if (hasCSS) {
                final DataSource ds = cssFiles.get(0);
                addCSS(nuggetPath, resolver, ds.getInputStream(), ds.getContentType(),
                    CommunitySiteConstants.PROP_THEME_ID, ds.getName());
            }
            boolean publishRunMode = this.isPublishMode();

            nugget.setProperty(SiteActivationService.REPLICATE_PUBLISH_RUN_MODE, Boolean.toString(publishRunMode));
            session.save();

            // reverse replicate nugget node
            if (resolver.getResource(nuggetPath) != null && publishRunMode) {
                final List paths = new ArrayList(2);
                paths.add(nuggetPath);
                paths.add(nuggetPath + "/" + PROPERTY_IMAGE_NAME);
                if (hasBanner) {
                    paths.add(nuggetPath + "/" + PROPERTY_BANNER_IMAGE_NAME);
                }
                if (hasCSS) {
                    paths.add(nuggetPath + "/" + CommunitySiteConstants.PROP_THEME_ID);
                }
                replicator.reverseReplicate(ReplicationActionType.ACTIVATE, paths);
            }

            /* Perform the wait with the msm-service as the user admin session we have can't read content. */
            ResourceResolver msmResolver = null;
            try {
                msmResolver =
                    getServiceUserWrapper().getServiceResourceResolver(getBundleLocalResourceResolverFactory(),
                        Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object) MSM_SERVICE));

                GroupUtil.waitForPageCreation(msmResolver, groupPath, 120 * 1000, 15 * 1000);
            } finally {
                if (msmResolver != null) {
                    msmResolver.close();
                }
            }

            final Resource communityGroupResource =
                root.getResourceResolver().resolve(groupPath + "/" + CommunityGroupConstants.CONFIG_NODE_NAME);
            final SocialComponentFactory factory =
                componentFactoryManager.getSocialComponentFactory(communityGroupResource);
            final CommunityGroup communityGroup =
                (CommunityGroup) ((factory != null) ? factory.getSocialComponent(communityGroupResource) : null);
            performAfterActions(createOperation, session, communityGroup, properties);

            return communityGroupResource;
        } catch (final OperationException e) {
            cleanupFailure(session);
            throw e;
        } catch (final Exception e) {
            cleanupFailure(session);
            throw new OperationException("Failed to create community group.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }

    }

    @Override
    public Resource update(final SlingHttpServletRequest request) throws OperationException {

        final Resource resource = request.getResource();

        final Session session = resource.getResourceResolver().adaptTo(Session.class);
        final CommunityContext context = resource.adaptTo(CommunityContext.class);
        if (context.getCommunityGroupId() == null) {
            throw new OperationException("Not a valid Group resource", HttpServletResponse.SC_BAD_REQUEST);
        }

        if (!siteService.mayPost(resource, session)) {
            throw new OperationException("Access violation.", HttpServletResponse.SC_FORBIDDEN);
        }
        String name;
        CommunityGroup group = (CommunityGroup) this.getCommunityGroupComponentForResource(resource, request);
        if (group != null) {
            name = group.getUrl();
        } else {
            name = resource.getName();
        }
        final Map props = new HashMap();
        final JSONArray functions;
        try {
            getDefaultProperties(request, props, false);
            getCustomProperties(request, props, session);
            final String structureString = (String) props.get(CommunitySiteConstants.PROP_FUNCTIONS);
            if (!validateFunctionValue(structureString)){
                LOG.error("Malformed Function property "+ structureString);
                throw new OperationException("Malformed Function property ", HttpServletResponse.SC_BAD_REQUEST);
            }
            final JSONObject structure = new JSONObject(structureString);
            functions = structure.getJSONArray(CommunitySiteConstants.PROP_FUNCTIONS);
        } catch (final RepositoryException e) {
            throw new OperationException("Failed to obtain community group properties.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (JSONException e1) {
            throw new OperationException("Failed to create community site.", e1,
                    HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
        try {
            FunctionValidationUtil.validateFunctions(resource.getParent(), functions );
        } catch (FunctionValidationException e) {
            LOG.error("Community functions malformed", e);
            // checkstyle ignore:line
            throw new OperationException("Community functions malformed\n" + e.getMessage(),
                    HttpServletResponse.SC_CONFLICT);
        }

        // get the attachment images
        final List attachments =
            getAttachmentsFromRequest(request, CommunityGroupConstants.PROP_COMMUNITY_GROUP_FILE);
        if (attachments != null && !attachments.isEmpty()) {
            props.put(CommunityGroupConstants.PROP_COMMUNITY_GROUP_FILE, attachments);
        }
        // get the banner images
        final List banner =
            getAttachmentsFromRequest(request, CommunityGroupConstants.REQUEST_ATTACHMENT_PAGEBANNER);
        final List groupCSS =
            getAttachmentsFromRequest(request, CommunitySiteConstants.REQUEST_ATTACHMENT_PAGECSS);
        final List groupThumbnail =
            getAttachmentsFromRequest(request, CommunitySiteConstants.REQUEST_ATTACHMENT_THUMBNAIL);
        if (banner != null && !banner.isEmpty())
            props.put(CommunityGroupConstants.REQUEST_ATTACHMENT_PAGEBANNER, banner);

        final U updateOperation = getUpdateOperation();
        getExtensionParameters(updateOperation, request, props);
        // update the site
        ResourceResolver msmResolver = null;
        ResourceResolver userManagerResolver = null;
        // ResourceResolver aclResolver = null;
        try {
            msmResolver = serviceUserWrapper.getServiceResourceResolver(resourceResolverFactory,
                Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object) MSM_SERVICE));
            userManagerResolver = serviceUserWrapper.getServiceResourceResolver(resourceResolverFactory,
                Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object) USER_ADMIN));
            // aclResolver =
            // serviceUserWrapper.getServiceResourceResolver(resourceResolverFactory,
            // Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object) ACL_MANAGER));
            return update(siteBlueprintService, resource, group, props, banner, groupThumbnail, groupCSS, session,
                msmResolver, userManagerResolver, funcDefUtils);
        } catch (final LoginException e) {
            LOG.error("Failed to login as service user.", e);
            throw new OperationException(e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } finally {
            if (msmResolver != null) {
                msmResolver.close();
            }
            if (userManagerResolver != null) {
                userManagerResolver.close();
            }

        }

    }

    // this method does not validate access right.
    protected Resource update(final TemplateRolloutService siteBlueprintService, final Resource root,
        final CommunityGroup group, final Map properties, final List attachments,
        final List groupThumbnail, final List groupCSS, final Session session,
        final ResourceResolver msmResolver, final ResourceResolver userManagerResolver,
        final FunctionDefinitionUtils funcDefUtils) throws OperationException {
        if (ResourceUtil.isNonExistingResource(root)) {
            throw new OperationException("Invalid group resource '" + root.getPath() + "'",
                HttpServletResponse.SC_BAD_REQUEST);
        }
        if (group == null) {
            throw new OperationException("Invalid community group " + root.getPath(),
                HttpServletResponse.SC_BAD_REQUEST);
        }

        try {
            final U updateOperation = getUpdateOperation();
            performBeforeActions(updateOperation, session, root, properties);
            final Resource siteRoot = updateGroup(siteBlueprintService, group, attachments, groupThumbnail, groupCSS,
                properties, msmResolver, userManagerResolver, funcDefUtils);
            if (msmResolver.hasChanges()) {
                msmResolver.commit();
            }
            final SocialComponentFactory factory = componentFactoryManager.getSocialComponentFactory(root);

            final CommunityGroup updatedGroup =
                (CommunityGroup) ((factory != null) ? factory.getSocialComponent(root) : null);

            // siteConfigurator.setSiteLevelConfiguration(updatedSite, msmResolver, aclResolver, userManagerResolver);
            performAfterActions(updateOperation, session, updatedGroup, properties);
            return siteRoot;
        } catch (final OperationException e) {
            cleanupFailure(session);
            throw e;
        } catch (final Exception e) {
            cleanupFailure(session);
            throw new OperationException("Failed to update community site.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }

    }

    private boolean validateFunctionValue(final String structureString) {
        if (StringUtils.isBlank(structureString)) {
            return false;
        }
        try {
            final JSONObject structure = new JSONObject(structureString);
            final JSONArray functions = structure.getJSONArray(CommunitySiteConstants.PROP_FUNCTIONS);
            return functions.length() > 0;
        } catch (JSONException e) {
            LOG.debug("Malformed json for Function property {}", structureString, e);
            return false;
        }
    }

    private void updateGroupStructure(final TemplateRolloutService siteBlueprintService, final Node configNode,
        final Resource site, final String structureString, final ResourceResolver resolver,
        final FunctionDefinitionUtils funcDefUtils)
        throws JSONException, RepositoryException, PersistenceException, WCMException, OperationException {
        final JSONObject structure = new JSONObject(structureString);
        if (structure.has(CommunitySiteConstants.PROP_FUNCTIONS)) {
            final PageManager pageMgr = resolver.adaptTo(PageManager.class);
            final JSONArray functions = structure.getJSONArray(CommunitySiteConstants.PROP_FUNCTIONS);
            final JSONArray newStructure = new JSONArray();
            final List deletedPages = new ArrayList(3);
            if (functions.length() > 0) {
                SiteOperationUtils groupOperationUtils = SiteOperationUtils.getInstance();
                groupOperationUtils.applyActions(siteBlueprintService, site, resolver, funcDefUtils, functions,
                    newStructure);
                groupOperationUtils.reorderFunctions(siteBlueprintService, functions, site, resolver,
                    configNode.getProperty(CommunitySiteConstants.PROP_SITE_BLUEPRINT_ID).getString(), deletedPages);
                final Node functionsNode = funcDefUtils.createFunctions(newStructure, configNode, resolver);
                groupOperationUtils.deleteFunctions(functionsNode, deletedPages, site, pageMgr, resolver);
                groupOperationUtils.applyConfigurations(siteBlueprintService, functionsNode, site, resolver);
            }
        }
    }

    // this method does not validate access right.
    protected Resource updateGroup(final TemplateRolloutService siteBlueprintService, final CommunityGroup group,
        final List attachments, final List groupThumbnail, final List siteCSS,
        final Map properties, final ResourceResolver msmResolver,
        final ResourceResolver userManagerResolver, final FunctionDefinitionUtils funcDefUtils)
        throws OperationException {
        final ResourceResolver resolver = group.getResource().getResourceResolver();
        final Session session = resolver.adaptTo(Session.class);
        try {
            final Session msmSession = msmResolver.adaptTo(Session.class);
            final Session userManagerSession = userManagerResolver.adaptTo(Session.class);
            String siteName = (String) properties.get(CommunitySiteConstants.PROP_SITE_NAME);
            final CommunityContext context = group.getResource().adaptTo(CommunityContext.class);

            /**
             * The resource is pointing to the configuration node.
             */
            final Node configNode = msmSession.getNode(group.getResource().getPath());
            String title = (String) properties.get(CommunityGroupConstants.PROP_COMMUNITY_GROUP_TITLE);
            final String description =
                (String) properties.get(CommunityGroupConstants.PROP_COMMUNITY_GROUP_DESCRIPTION);
            final String type = (String) properties.get(CommunityGroupConstants.PROP_COMMUNITY_GROUP_TYPE);

            if (StringUtils.isBlank(siteName)) {
                siteName = (String) properties.get(CommunitySiteConstants.PROP_SITE_URL_NAME);
            }
            String currentThemeId = "";
            if (configNode.hasProperty(CommunitySiteConstants.PROP_THEME_ID)) {
                currentThemeId = configNode.getProperty(CommunitySiteConstants.PROP_THEME_ID).getString();
            }
            if (StringUtils.isBlank(currentThemeId)) {
                currentThemeId = CommunitySiteConstants.DEFAULT_CONFIG_THEME;
            }
            final U operation = getUpdateOperation();
            final Resource groupRoot = msmResolver.getResource(group.getResource().getPath()).getParent();
            for (final Entry property : properties.entrySet()) {
                final String key = property.getKey();
                if (isConfigurationParameter(operation, key)
                        && isUpdatedParameter(key, property.getValue(), configNode)) {
                    if (!ArrayUtils.contains(fieldWhitelist, key)) {
                        if (StringUtils.equals(CommunitySiteConstants.PROP_FUNCTIONS, key)) {
                            if (validateFunctionValue((String) property.getValue())) {  // don't want to store corrupt
                                                                                        // value of functions
                                JcrUtil.setProperty(configNode, key, property.getValue());
                            }
                        } else {
                            JcrUtil.setProperty(configNode, key, property.getValue());
                        }
                    } else {
                        // Have to do this when a string value is replaced by multivalue prop
                        // This is possible when a person enters one tag first and later adds another
                        // In the above situation initially its stores as string and later it needs to be changed
                        // multivalue
                        if (configNode.hasProperty(key)) {
                            if (!configNode.getProperty(key).getClass().equals(property.getValue().getClass())) {
                                configNode.getProperty(key).remove();
                                JcrUtil.setProperty(configNode, key, property.getValue());
                            } else {
                                JcrUtil.setProperty(configNode, key, property.getValue());
                            }
                        } else {
                            JcrUtil.setProperty(configNode, key, property.getValue());
                        }
                    }
                }
            }

            ValueMap props = new ValueMapDecorator(properties);
            props.put(CommunityGroupConstants.PROP_COMMUNITY_GROUP_BLUEPRINT_ID,
                configNode.getProperty(CommunitySiteConstants.PROP_SITE_BLUEPRINT_ID).getString());
            //CQ-4220011 Need not change the group unique id of the group
            props.put(CommunityGroupConstants.PROP_GROUP_ID, configNode.getProperty(CommunityGroupConstants.PROP_GROUP_ID).getString());
            // do not change invite list, so read it from configuration node
            if (configNode.hasProperty(CommunityGroupConstants.PROP_COMMUNITY_GROUP_INVITE)) {
                props.put(CommunityGroupConstants.PROP_COMMUNITY_GROUP_INVITE,
                    configNode.getProperty(CommunityGroupConstants.PROP_COMMUNITY_GROUP_INVITE).getString());
            }
            // do not change group type, so read it from configuration node
            props.put(CommunityGroupConstants.PROP_COMMUNITY_GROUP_TYPE,
                configNode.getProperty(CommunityGroupConstants.PROP_COMMUNITY_GROUP_TYPE).getString());
            props.put(CommunityGroupConstants.PROP_COMMUNITY_GROUP_NAME,
                configNode.getProperty(CommunityGroupConstants.PROP_COMMUNITY_GROUP_NAME).getString());
            props.put(CommunityGroupConstants.PROP_COMMUNITY_GROUP_CREATOR,
                configNode.getProperty(CommunityGroupConstants.PROP_COMMUNITY_GROUP_CREATOR).getString());
            communityGroupService.prepareUserGroups(userManagerResolver, groupRoot, groupRoot.getParent().getPath(),
                props);
            
            final String structureString = (String) properties.get(CommunitySiteConstants.PROP_FUNCTIONS);
            if (!org.apache.commons.lang3.StringUtils.isEmpty(structureString)) {
                try {
                    updateGroupStructure(siteBlueprintService, configNode, group.getResource().getParent(),
                        structureString, msmResolver.getResource(group.getResource().getPath()).getResourceResolver(), funcDefUtils);
                    // overwrite the title property for root page of sites
                    final ModifiableValueMap pageProps = userManagerResolver.getResource(group.getResource().getParent().getPath()).getChild(NameConstants.NN_CONTENT).adaptTo(ModifiableValueMap.class);
                    pageProps.put(NameConstants.PN_TITLE, siteName);
                    pageProps.put(NameConstants.PN_PAGE_TITLE, siteName);
                } catch (final JSONException e) {
                    throw new OperationException("Malformed group structure", e, HttpServletResponse.SC_BAD_REQUEST);
                }
            }
            communityGroupService.setCommunityGroupConfigure(msmResolver, groupRoot.getPath(), props);
            // Take care of theme and theme images
            // TODO: Hack this to get the blueprint to work. Move it to the site
            // photo folder.
            final Node photoFolder;
            if (configNode.getParent().hasNode("photos")) {
                photoFolder = configNode.getParent().getNode("photos");
            } else {
                photoFolder = configNode.getParent().addNode("photos", "sling:Folder");
            }
            final String themeId =
                PropertiesUtils.get(properties, CommunitySiteConstants.PROP_THEME_ID, currentThemeId);
            if (group.isUsingCustomCSS()) {
                if (!themeId.equals(currentThemeId)) {
                    try {
                        final Node themeNode = session.getNode(currentThemeId);
                        if (themeNode != null) {
                            themeNode.remove();
                        }
                    } catch (PathNotFoundException e) {
                        LOG.error("Path not found", e);
                    }
                }
            }
            Node image = null;
            String photoFolderPath = photoFolder.getPath();

            for (final DataSource ds : groupThumbnail) {
                image = addThemeAsset(userManagerResolver, context, photoFolderPath, ds.getName(),
                    resolver.adaptTo(Session.class).getValueFactory().createBinary(ds.getInputStream()),
                    ds.getContentType());
            }
            final Node groupThumbnailImage = createThumbnailAsset(groupThumbnail, groupRoot, userManagerResolver, context);
            final Node groupCSSNode = createCSSAsset(siteCSS, userManagerSession, context);
            if (groupCSSNode != null) {
                JcrUtil.setProperty(configNode, CommunitySiteConstants.PROP_THEME_ID, groupCSSNode.getPath());
            } else {
                final String themeProp = PropertiesUtils.get(properties, CommunitySiteConstants.PROP_THEME_ID,
                    CommunitySiteConstants.DEFAULT_CONFIG_THEME);
                JcrUtil.setProperty(configNode, CommunitySiteConstants.PROP_THEME_ID, themeProp);
            }

            // Take care of banner
            updateImage(groupRoot, attachments, CommunityGroupConstants.REQUEST_ATTACHMENT_PAGEBANNER);
            updateImage(groupRoot,
                (List) properties.get(CommunityGroupConstants.PROP_COMMUNITY_GROUP_FILE),
                GroupConstants.PROPERTY_IMAGE_NAME);
            // Add last modified
            JcrUtil.setProperty(configNode, JcrConstants.JCR_LASTMODIFIED, Calendar.getInstance());
            session.save();
            return resolver.getResource(configNode.getPath());
        } catch (final Exception e) {
            LOG.error("Error updating Community site.", e);
            throw new OperationException("Error updating community site.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

        }

    }

    @Nullable
    private Node createCSSAsset(final List siteThumbnail, final Session session,
        final CommunityContext context) throws RepositoryException, IOException {
        final String absolutePath = context.getPageThemePath();
        final String nodeType = "cq:ClientLibraryFolder";

        for (final DataSource cssFile : siteThumbnail) {
            final boolean isCss = cssFile.getContentType().equalsIgnoreCase(CommunitySiteConstants.CSS_MIMETYPE);
            if (isCss) {
                final String assetName = JcrUtil.createValidName(cssFile.getName());
                // Create node /etc/designs/tenants/tenant_id/site_id/sitethemes
                // or
                // /etc/designs/site_id/sitethemes
                final Node assetRootNode =
                    JcrUtil.createPath(absolutePath, false, CommunitySiteConstants.SITES_ROOT_NODETYPE,
                        CommunitySiteConstants.SITES_ROOT_NODETYPE, session, false);
                final Node assetNode = JcrUtil.createUniqueNode(assetRootNode, assetName, nodeType, session);
                final Node cssNode = JcrUtils.putFile(assetNode, assetName, CommunitySiteConstants.CSS_MIMETYPE,
                    cssFile.getInputStream());
                // TODO: Move to ugc community?
                String themeCategoryName = CommunitySiteConstants.CUSTOM_THEME_PREFIX
                        + context.getCommunityGroupUniqueId() + "." + assetNode.getName();
                if (context.isMultiTenantSupported()) {
                    themeCategoryName = CommunitySiteConstants.CUSTOM_THEME_PREFIX + context.getTenantId() + "."
                            + context.getSiteId() + "." + assetNode.getName();
                }
                JcrUtil.setProperty(assetNode, CommunitySiteConstants.PROP_THEME_CATEGORIES,
                    new String[]{themeCategoryName});
                JcrUtils.putFile(assetNode, "css.txt", CommunitySiteConstants.TEXT_MIMETYPE,
                    new ByteArrayInputStream(cssNode.getName().getBytes()));
                return assetNode;
            }
        }
        return null;
    }

    /**
     * Add a theme asset for the specified tenant
     * @param resolver ResourceResolver
     * @param context CommunityContext
     * @param photoFolderPath folder for storing image
     * @param name Name
     * @param data binary data
     * @param contentType type of the content
     * @return node value
     * @throws OperationException if theme is not added properly
     */
    protected static Node addThemeAsset(final ResourceResolver resolver, final CommunityContext context,
                                        final String photoFolderPath, final String name, final Binary data, final String contentType)
            throws OperationException {
        final Session session = resolver.adaptTo(Session.class);
        try {
            final Node photoRootNode =
                    JcrUtils.getOrCreateByPath(photoFolderPath,CommunitySiteConstants.SITES_ROOT_NODETYPE,resolver.adaptTo(Session.class));
            if (photoRootNode.hasNode(PROPERTY_IMAGE_NAME)) {
                photoRootNode.getNode(PROPERTY_IMAGE_NAME).remove();
            }
            final String assetRootName;
            return createFile(resolver, PROPERTY_IMAGE_NAME, data, contentType, photoRootNode,
                    "", name, context);

        } catch (final RepositoryException e) {
            try {
                session.refresh(false);
            } catch (final RepositoryException e1) {
                LOG.error("Repository error while adding theme asset.");
                throw new OperationException("Repository error while adding theme asset.",
                        HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            }
            LOG.error("Repository error while adding theme asset.");
            throw new OperationException("Repository error while adding theme asset.",
                    HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }

    private Node createThumbnailAsset(final List siteThumbnail, final Resource site,
        final ResourceResolver resolver, final CommunityContext context) throws RepositoryException, IOException {
        if (siteThumbnail != null && !siteThumbnail.isEmpty()) {
            final Node siteNode = site.getChild(JcrConstants.JCR_CONTENT).adaptTo(Node.class);
            if (siteNode.hasNode(CommunitySiteConstants.IMAGE_NODENAME)) {
                // Hack to generate a newer thumbnail
                // Thumbnail doesnt get updated when creating a new file
                final Node fileNode = siteNode.getNode(CommunitySiteConstants.IMAGE_NODENAME);
                fileNode.remove();
                resolver.adaptTo(Session.class).save();
            }
            for (final DataSource ds : siteThumbnail) {
                return createFile(resolver, CommunitySiteConstants.FILE_NODENAME,
                    resolver.adaptTo(Session.class).getValueFactory().createBinary(ds.getInputStream()),
                    ds.getContentType(), siteNode, CommunitySiteConstants.IMAGE_NODENAME, ds.getName(), context);
            }
        }
        return null;
    }

    protected static Node createFile(final ResourceResolver resolver, final String name, final Binary data,
        final String contentType, final Node themeIdNode, final String assetRootName, final String fileNameProperty,
        final CommunityContext context) throws RepositoryException {
        final Session session = resolver.adaptTo(Session.class);
        final boolean isCss = contentType.equalsIgnoreCase(CommunitySiteConstants.CSS_MIMETYPE);
        final String nodeType = JcrConstants.NT_UNSTRUCTURED;
        final boolean createUniqueLeaf = false;
        final String aRootName = assetRootName;
        final Node assetRootNode = JcrUtil.createPath(themeIdNode, aRootName, createUniqueLeaf, // createUniqueLeaf
            JcrConstants.NT_UNSTRUCTURED, // intermediateNodeType
            nodeType, // nodeType
            session, false // autoSave
        );
        Node assetNode;
        Node resNode;
        String assetName = name;
        if (assetName == null) {
            assetName = "a" + System.currentTimeMillis() + ".bin";
        } else {
            int pos = assetName.lastIndexOf('/');
            if (pos < 0) {
                pos = assetName.lastIndexOf('\\');
            }
            if (pos >= 0) {
                assetName = name.substring(pos + 1);
            }
        }
        String assetContentType = contentType;
        if (assetContentType == null) {
            assetContentType = "application/octet-stream";
        }
        // check if content-type has a charset
        final int pos = assetContentType.indexOf(';');
        if (pos > 0) {
            // for now, just snip off
            assetContentType = assetContentType.substring(0, pos);
        }
        String fileNameWithoutExtension = FilenameUtils.getBaseName(assetName);
        final String fileNameExtension = FilenameUtils.getExtension(assetName);

        fileNameWithoutExtension = JcrUtil.isValidName(fileNameWithoutExtension) ? fileNameWithoutExtension
            : JcrUtil.createValidName(fileNameWithoutExtension);
        String fileName = "";
        if (StringUtils.isBlank(fileNameExtension)) {
            fileName = fileNameWithoutExtension;
        } else {
            fileName = fileNameWithoutExtension + "." + fileNameExtension;
        }
        if (assetRootNode.hasNode(fileName)) {
            assetNode = assetRootNode.getNode(fileName);
            resNode = assetNode.getNode(JcrConstants.JCR_CONTENT);
        } else {
            // TODO: Do we want to use comment attachment node type?
            assetNode = assetRootNode.addNode(fileName, CommentSystem.COMMENT_ATTACHMENT_NODETYPE);
            resNode = assetNode.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE);
        }
        resNode.setProperty(JcrConstants.JCR_MIMETYPE, assetContentType);
        resNode.setProperty(JcrConstants.JCR_DATA, data);
        resNode.setProperty(JcrConstants.JCR_LASTMODIFIED, Calendar.getInstance());
        assetNode.setProperty(CommunitySiteConstants.PROP_SITE_NAME, fileNameProperty);

        return assetNode;
    }

    private Node getThemeNode(final CommunityContext context, final String themeId, final Session session)
        throws PathNotFoundException, RepositoryException {
        final String themePath = context.getSiteThemePath() + +CommunityConstants.FORWARDSLASH_CHAR + themeId;
        return session.getNode(themePath);
    }

    protected void updateImage(final Resource groupRoot, final List dataSources, final String name)
        throws OperationException {
        if (dataSources != null && !dataSources.isEmpty()) {
            Resource imgResource = groupRoot.getChild(SiteBlueprintConstants.PROPERTY_IMAGE_PATH + "/" + name);
            DataSource dataSource = dataSources.get(0);
            try {
                if (imgResource != null) {
                    final Node bannerNode = imgResource.adaptTo(Node.class);
                    if (bannerNode != null && StringUtils.isNotBlank(dataSource.getName())) {
                        bannerNode.setProperty(NAME, dataSource.getName());
                    }
                    final Node imageContentNode = bannerNode.getNode(JcrConstants.JCR_CONTENT);
                    final Binary data = groupRoot.getResourceResolver().adaptTo(Session.class).getValueFactory()
                        .createBinary(dataSource.getInputStream());
                    imageContentNode.setProperty(JcrConstants.JCR_MIMETYPE, dataSource.getContentType());
                    imageContentNode.setProperty(JcrConstants.JCR_DATA, data);
                    imageContentNode.setProperty(JcrConstants.JCR_LASTMODIFIED, Calendar.getInstance());
                } else {
                    // new content
                    Resource photos = groupRoot.getChild(SiteBlueprintConstants.PROPERTY_IMAGE_PATH);

                    if (photos == null) {
                        Node groupRootNode = groupRoot.adaptTo(Node.class);
                        groupRootNode.addNode(SiteBlueprintConstants.PROPERTY_IMAGE_PATH,
                            JcrResourceConstants.NT_SLING_FOLDER);
                    }
                    addImage(groupRoot.getPath() + "/" + SiteBlueprintConstants.PROPERTY_IMAGE_PATH,
                        groupRoot.getResourceResolver(), dataSource.getInputStream(), dataSource.getContentType(),
                        name, dataSource.getName());
                }
            } catch (RepositoryException e) {
                throw new OperationException("Failed to create image " + name, e,
                    HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            } catch (IOException e) {
                throw new OperationException("Failed to read image " + name, e,
                    HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            }
        }
    }

    protected boolean isConfigurationParameter(final U operation, final String parameterName)
        throws OperationException {
        for (final String key : unchangeableParams) {
            if (key.equals(parameterName)) {
                return false;
            }
        }
        if (this.extensionProviders.containsKey(operation)) {
            for (final T extension : this.extensionProviders.get(operation)) {
                try {
                    return extension.isConfigurationParameter(operation, parameterName);
                } catch (final NoSuchMethodError e) {
                }  // Catch these exceptions just in case there are extensions which implements the old SiteOperation
                   // interface.
                catch (final AbstractMethodError e) {
                }
            }
        }
        return true;
    }

    @Override
    protected void getExtensionParameters(final U operation, final SlingHttpServletRequest request,
        final Map requestParameters) throws OperationException {
        if (this.extensionProviders.containsKey(operation)) {
            for (final T extension : this.extensionProviders.get(operation)) {
                try {
                    extension.getExtensionRequestParameters(operation, request, requestParameters);
                } catch (final NoSuchMethodError e) {
                }  // Catch these exceptions just in case there are extensions which implements the old SiteOperation
                   // interface.
                catch (final AbstractMethodError e) {
                }
            }
        }
    }

    private boolean isUpdatedParameter(final String key, final Object value, final Node configNode)
        throws RepositoryException {
        if (configNode.hasProperty(key)) {
            final Property prop = configNode.getProperty(key);
            if (!prop.isMultiple()) {
                final Value currentValue = prop.getValue();
                final Value newValue = JcrUtil.createValue(value, configNode.getSession());
                if (newValue != null) {
                    return !newValue.equals(currentValue);
                } else {
                    return true;
                }
            } else {
                return true;
            }
        } else {
            return true;
        }
    }

    @Override
    public boolean isPublishMode() {
        return settingsService != null && settingsService.getRunModes().contains("publish");
    }

    @Override
    public boolean approveJoin(final ResourceResolver resolver, final CommunityGroup group)
        throws OperationException {
        // anonymous cannot join
        final Session session = resolver.adaptTo(Session.class);
        if (StringUtils.endsWithIgnoreCase(session.getUserID(), "anonymous")) {
            return false;
        }
        // auto approve the request to join open community groups
        if (group != null && GroupConstants.TYPE_OPEN.equals(group.getType())) {
            return true;
        }
        // currently reject all other requests
        return false;
    }

    @Override
    public Resource join(final SlingHttpServletRequest request) throws OperationException {
        final Resource resource = request.getResource();
        final ResourceResolver resolver = resource.getResourceResolver();
        final Session session = resource.getResourceResolver().adaptTo(Session.class);
        final String groupUrl = request.getParameter(CommunityGroupConstants.PROP_COMMUNITY_GROUP_PATH);
        if (StringUtils.isBlank(groupUrl)) {
            return null;
        }
        final Resource groupPageResource = resolver.resolve(groupUrl);
        final CommunityGroup group =
            (CommunityGroup) this.getCommunityGroupComponentForResource(groupPageResource, request);
        return join(resolver, group, session);
    }

    protected Resource join(final ResourceResolver resolver, final CommunityGroup group, final Session session)
        throws OperationException {
        if (resolver == null || group == null || session == null) {
            return null;
        }

        if (!approveJoin(resolver, group)) {
            throw new OperationException(
                "Deny " + session.getUserID() + "'s request to join community group " + group.getName(),
                HttpServletResponse.SC_NOT_ACCEPTABLE);
        }

        ResourceResolver userAdminResolver = null;
        try {
            final String authorizableId = session.getUserID();
            userAdminResolver =
                getServiceUserWrapper().getServiceResourceResolver(getBundleLocalResourceResolverFactory(),
                    Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object) USER_ADMIN));
            groupService.addGroupMember(userAdminResolver, group.getMemberGroupId(), authorizableId);
            resolver.adaptTo(Session.class).refresh(false);
            resolver.refresh();
        } catch (final LoginException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to login as user admin.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (final GroupException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to join community group.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (final RepositoryException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to join community group.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } finally {
            if (userAdminResolver != null) {
                userAdminResolver.close();
            }
        }
        return group.getResource();
    }

    @Override
    public Resource leave(final SlingHttpServletRequest request) throws OperationException {
        final Resource resource = request.getResource();
        final ResourceResolver resolver = resource.getResourceResolver();
        final Session session = resource.getResourceResolver().adaptTo(Session.class);
        final String groupUrl = request.getParameter(CommunityGroupConstants.PROP_COMMUNITY_GROUP_PATH);
        if (StringUtils.isBlank(groupUrl)) {
            return null;
        }
        final Resource groupPageResource = resolver.resolve(groupUrl);
        final CommunityGroup group =
            (CommunityGroup) this.getCommunityGroupComponentForResource(groupPageResource, request);
        return leave(resolver, group, session);
    }

    protected Resource leave(final ResourceResolver resolver, final CommunityGroup group, final Session session)
        throws OperationException {
        if (resolver == null || group == null || session == null) {
            return null;
        }

        ResourceResolver userAdminResolver = null;
        try {
            final String authorizableId = session.getUserID();
            userAdminResolver =
                getServiceUserWrapper().getServiceResourceResolver(getBundleLocalResourceResolverFactory(),
                    Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object) USER_ADMIN));
            groupService.removeGroupMember(userAdminResolver, group.getMemberGroupId(), authorizableId);
            resolver.adaptTo(Session.class).refresh(false);
            resolver.refresh();
        } catch (final LoginException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to login as user admin.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (final GroupException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to join community group.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (final RepositoryException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to join community group.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } finally {
            if (userAdminResolver != null) {
                userAdminResolver.close();
            }
        }
        return group.getResource();
    }

    @Override
    public Resource invite(final SlingHttpServletRequest request) throws OperationException {
        final Resource resource = request.getResource();
        final ResourceResolver resolver = resource.getResourceResolver();
        final Session session = resource.getResourceResolver().adaptTo(Session.class);
        final String[] inviteList = request.getParameterValues(CommunityGroupConstants.PROP_COMMUNITY_GROUP_USERS);
        final CommunityContext context = resource.adaptTo(CommunityContext.class);
        final Resource groupPageResource = resolver.resolve(context.getCommunityGroupPath());
        final CommunityGroup group =
            (CommunityGroup) this.getCommunityGroupComponentForResource(groupPageResource, request);
        invite(resolver, group, inviteList, context, session);
        return resource;
    }

    protected void invite(final ResourceResolver resolver, final CommunityGroup group, final String[] inviteList,
        final CommunityContext context, final Session session) throws OperationException {
        if (resolver == null || group == null || session == null) {
            return;
        }
        ResourceResolver userAdminResolver = null;
        try {
            userAdminResolver =
                getServiceUserWrapper().getServiceResourceResolver(getBundleLocalResourceResolverFactory(),
                    Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object) USER_ADMIN));
            UserManager um = ((JackrabbitSession)userAdminResolver.adaptTo(Session.class)).getUserManager();
            if (!GroupUtil.canInviteGroupMember(resolver, context, serviceUserWrapper, repository, um)) {
                throw new OperationException(
                        "Deny " + session.getUserID() + "'s request to invite users to community group " + group.getName(),
                        HttpServletResponse.SC_NOT_ACCEPTABLE);
            }
            groupService.addGroupMembers(userAdminResolver, group.getMemberGroupId(), inviteList);
            session.refresh(false);
            resolver.refresh();
        } catch (final LoginException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to login as user admin.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (final GroupException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to join community group.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (final RepositoryException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to join community group.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } finally {
            if (userAdminResolver != null) {
                userAdminResolver.close();
            }
        }
    }

    @Override
    public Resource uninvite(final SlingHttpServletRequest request) throws OperationException {
        final Resource resource = request.getResource();
        final ResourceResolver resolver = resource.getResourceResolver();
        final Session session = resource.getResourceResolver().adaptTo(Session.class);
        final String invite = request.getParameter(CommunityGroupConstants.PROP_COMMUNITY_GROUP_USERS);
        if (StringUtils.isEmpty(invite)) {
            return null;
        }
        final String[] inviteList = invite.split(delimiters);
        final CommunityContext context = resource.adaptTo(CommunityContext.class);
        final Resource groupPageResource = resolver.resolve(context.getCommunityGroupPath());
        final CommunityGroup group =
            (CommunityGroup) this.getCommunityGroupComponentForResource(groupPageResource, request);
        uninvite(resolver, group, inviteList, context, session);
        return resource;
    }

    protected void uninvite(final ResourceResolver resolver, final CommunityGroup group, final String[] inviteList,
        final CommunityContext context, final Session session) throws OperationException {
        if (resolver == null || group == null || session == null) {
            return;
        }
        ResourceResolver userAdminResolver = null;
        try {
            userAdminResolver =
                getServiceUserWrapper().getServiceResourceResolver(getBundleLocalResourceResolverFactory(),
                    Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object) USER_ADMIN));
            UserManager um = ((JackrabbitSession)userAdminResolver.adaptTo(Session.class)).getUserManager();
            if (!GroupUtil.canInviteGroupMember(resolver, context, serviceUserWrapper, repository, um)) {
                throw new OperationException(
                        "Deny " + session.getUserID() + "'s request to invite users to community group " + group.getName(),
                        HttpServletResponse.SC_NOT_ACCEPTABLE);
            }
            groupService.removeGroupMembers(userAdminResolver, group.getMemberGroupId(), inviteList);
            session.refresh(false);
            resolver.refresh();
        } catch (final LoginException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to login as user admin.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (final GroupException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to uninvite member from community group.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (final RepositoryException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to uninvite member from community group.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } finally {
            if (userAdminResolver != null) {
                userAdminResolver.close();
            }
        }
    }

    @Override
    public Resource promoteMember(final SlingHttpServletRequest request) throws OperationException {
        final Resource resource = request.getResource();
        final ResourceResolver resolver = resource.getResourceResolver();
        final Session session = resource.getResourceResolver().adaptTo(Session.class);
        final String[] inviteList = request.getParameterValues(CommunityGroupConstants.PROP_COMMUNITY_GROUP_USERS);
        final CommunityContext context = resource.adaptTo(CommunityContext.class);
        final Resource groupPageResource = resolver.resolve(context.getCommunityGroupPath());
        final CommunityGroup group =
            (CommunityGroup) this.getCommunityGroupComponentForResource(groupPageResource, request);
        LOG.debug("promoteMember: resource {} groupPageResource {}", resource, groupPageResource);
        LOG.debug("promoteMember: communityGroupPath {}", context.getCommunityGroupPath());
        promoteMember(resolver, group, context, inviteList, session);
        return resource;
    }

    protected void promoteMember(final ResourceResolver resolver, final CommunityGroup group,
        final CommunityContext context, final String[] inviteList, final Session session) throws OperationException {
        final String groupAdminID = context.getSiteUserGroupName(CommunityUserGroup.GROUP_ADMIN);
        if (LOG.isDebugEnabled()) {
            LOG.debug("promoteMember: {} to {}", Arrays.asList(inviteList), groupAdminID);
        }
        if (resolver == null) {
            throw new IllegalArgumentException("resolver not allowed to be null");
        }
        if (group == null) {
            throw new IllegalArgumentException("group not allowed to be null");
        }
        if (session == null) {
            throw new IllegalArgumentException("session not allowed to be null");
        }
        LOG.debug("promoteMember: {} is authorized.", resolver.getUserID());

        ResourceResolver userAdminResolver = null;
        try {
            userAdminResolver =
                getServiceUserWrapper().getServiceResourceResolver(getBundleLocalResourceResolverFactory(),
                    Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object) USER_ADMIN));
            UserManager um = ((JackrabbitSession)userAdminResolver.adaptTo(Session.class)).getUserManager();
            if (!GroupUtil.canPromoteGroupMember(resolver, context, serviceUserWrapper, repository, um)) {
                throw new OperationException(
                        "Deny " + session.getUserID() + "'s request to invite users to community group " + group.getName(),
                        HttpServletResponse.SC_NOT_ACCEPTABLE);
            }
            LOG.debug("promoteMember: calling addGroupMembers adding {} to {}.", Arrays.asList(inviteList),
                groupAdminID);
            groupService.addGroupMembers(userAdminResolver, groupAdminID, inviteList);
            session.refresh(false);
            resolver.refresh();
        } catch (final LoginException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to login as user admin.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (final GroupException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to join community group.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (final RepositoryException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to join community group.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } finally {
            if (userAdminResolver != null) {
                userAdminResolver.close();
            }
        }
    }

    @Override
    public Resource demoteMember(final SlingHttpServletRequest request) throws OperationException {
        final Resource resource = request.getResource();
        final ResourceResolver resolver = resource.getResourceResolver();
        final Session session = resource.getResourceResolver().adaptTo(Session.class);
        final String[] inviteList = request.getParameterValues(CommunityGroupConstants.PROP_COMMUNITY_GROUP_USERS);
        final CommunityContext context = resource.adaptTo(CommunityContext.class);
        final Resource groupPageResource = resolver.resolve(context.getCommunityGroupPath());
        final CommunityGroup group =
            (CommunityGroup) this.getCommunityGroupComponentForResource(groupPageResource, request);
        demoteMember(resolver, group, context, inviteList, session);
        return resource;
    }

    protected void demoteMember(final ResourceResolver resolver, final CommunityGroup group,
        final CommunityContext context, final String[] inviteList, final Session session) throws OperationException {
        if (resolver == null || group == null || session == null) {
            return;
        }
        final String groupAdminID = context.getSiteUserGroupName(CommunityUserGroup.GROUP_ADMIN);
        ResourceResolver userAdminResolver = null;
        try {
            userAdminResolver =
                getServiceUserWrapper().getServiceResourceResolver(getBundleLocalResourceResolverFactory(),
                    Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object) USER_ADMIN));
            UserManager um = ((JackrabbitSession)userAdminResolver.adaptTo(Session.class)).getUserManager();
            if (!GroupUtil.canPromoteGroupMember(resolver, context, serviceUserWrapper, repository, um)) {
                throw new OperationException(
                        "Deny " + session.getUserID() + "'s request to invite users to community group " + group.getName(),
                        HttpServletResponse.SC_NOT_ACCEPTABLE);
            }
            groupService.removeGroupMembers(userAdminResolver, groupAdminID, inviteList);
            groupService.addGroupMembers(userAdminResolver, group.getMemberGroupId(), inviteList);
            session.refresh(false);
            resolver.refresh();
        } catch (final LoginException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to login as user admin.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (final GroupException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to join community group.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } catch (final RepositoryException e) {
            cleanupFailure(session);
            throw new OperationException("Failed to join community group.", e,
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        } finally {
            if (userAdminResolver != null) {
                userAdminResolver.close();
            }
        }
    }

    private boolean isSpecialRequestParam(final String paramName) {
        for (int i = 0; i < specialParams.length; i++) {
            if (paramName.equals(specialParams[i])) {
                return true;
            }
        }
        return false;
    }

    protected void addCSS(final String path, final ResourceResolver resolver, final InputStream imageStream,
        final String contentType, final String nodeName, final String nameProperty)
        throws OperationException, RepositoryException {
        try {
            if (imageStream != null && imageStream.available() > 0) {
                final Resource folder = ResourceUtil.getOrCreateResource(resolver, path,
                    CommunityGroupConstants.FOLDER_NODETYPE, null, true);
                final Node folderNode = folder.adaptTo(Node.class);
                final Node groupthemesFolder = folderNode.addNode(GroupConstants.PROPERTY_GROUPTHEMES);
                final Node cssNode = groupthemesFolder.addNode(nameProperty);
                final Node cssFiles =
                    JcrUtils.putFile(cssNode, nameProperty, CommunitySiteConstants.CSS_MIMETYPE, imageStream);
                JcrUtils.putFile(cssNode, "css.txt", CommunitySiteConstants.TEXT_MIMETYPE,
                    new ByteArrayInputStream(cssFiles.getName().getBytes()));
            }
        } catch (final IOException e) {
            throw new OperationException("IO failure", e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }

    protected void addImage(final String path, final ResourceResolver resolver, final InputStream imageStream,
        final String contentType, final String nodeName, final String nameProperty)
        throws OperationException, RepositoryException {
        try {
            if (imageStream != null && imageStream.available() > 0) {
                final Resource folder = ResourceUtil.getOrCreateResource(resolver, path,
                    CommunityGroupConstants.FOLDER_NODETYPE, null, true);
                final Node folderNode = folder.adaptTo(Node.class);
                final Node imageNode = folderNode.addNode(nodeName, NODE_PROPERTY_COMMENTATTACHMENT);
                JcrUtil.setProperty(imageNode, NAME, nameProperty);
                final Node imageContentNode = imageNode.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE);
                final Binary data = resolver.adaptTo(Session.class).getValueFactory().createBinary(imageStream);
                imageContentNode.setProperty(JcrConstants.JCR_MIMETYPE, contentType);
                imageContentNode.setProperty(JcrConstants.JCR_DATA, data);
                imageContentNode.setProperty(JcrConstants.JCR_LASTMODIFIED, Calendar.getInstance());
            }
        } catch (final IOException e) {
            throw new OperationException("IO failure", e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }

        // NOTE: caller should save the new image node
    }

    protected void replaceImage(final Resource root, final String name, final ResourceResolver resolver,
        final InputStream imageStream, final String contentType) throws OperationException, RepositoryException {
        try {
            if (imageStream != null && imageStream.available() > 0) {

                final Resource folder = root.getChild(CommunityGroupConstants.FOLDER_NODETYPE);
                final Node folderNode = folder.adaptTo(Node.class);
                final Node imageNode = folderNode.getNode(name);
                final Node imageContentNode = imageNode.addNode(JcrConstants.JCR_CONTENT, JcrConstants.NT_RESOURCE);
                final Binary data = resolver.adaptTo(Session.class).getValueFactory().createBinary(imageStream);
                imageContentNode.setProperty(JcrConstants.JCR_MIMETYPE, contentType);
                imageContentNode.setProperty(JcrConstants.JCR_DATA, data);
                imageContentNode.setProperty(JcrConstants.JCR_LASTMODIFIED, Calendar.getInstance());
            }
        } catch (final IOException e) {
            throw new OperationException("IO failure", e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }

        // NOTE: caller should save the new image node
    }

    protected List getAttachmentsFromRequest(final SlingHttpServletRequest request,
        final String requestParameterName) {
        final RequestParameter[] fileRequestParameters = request.getRequestParameters(requestParameterName);
        if (fileRequestParameters != null) {
            // Didn't find equivalent in SocialUitl
            return CollabUtil.getAttachmentsFromRequest(fileRequestParameters, ATTACHMENT_FILE_LIMIT, WHITE_LIST,
                BLACK_LIST);
        }
        return Collections.emptyList();
    }

    /**
     * Extract the default site properties from the specified {@link SlingHttpServletRequest} into the specified.
     * {@link Map}
     * @param request SlingHttpServletRequest
     * @param props map of values
     * @param validateRequired gives validation status
     * @throws RepositoryException - thrown if unable to get props.
     * @throws OperationException - thrown if unable to get props
     */
    protected void getDefaultProperties(final SlingHttpServletRequest request, final Map props,
        final boolean validateRequired) throws RepositoryException, OperationException {

        for (int i = 0; i < requestParams.length; i++) {
            final Object params[] = requestParams[i];
            final Class clazz = (Class) params[PARAM_CLASS_INDEX];
            final String name = (String) params[PARAM_NAME_INDEX];
            if (clazz.isArray()) {
                final String values[] = request.getParameterValues(name);
                if (validateRequired && values == null && ((Boolean) params[PARAM_REQUIRED_INDEX])) {
                    throw new OperationException("Community group value '" + name + "' is empty",
                        HttpServletResponse.SC_BAD_REQUEST);

                }
                if (clazz == String[].class) {
                    if (values != null) {
                        props.put(name, StringUtils.join(values, ","));
                    }
                }
                // TODO: Handle other array types
            } else {
                final String value = request.getParameter(name);
                if (validateRequired && value == null && ((Boolean) params[PARAM_REQUIRED_INDEX])) {
                    throw new OperationException("Community group value '" + name + "' is empty",
                        HttpServletResponse.SC_BAD_REQUEST);
                }
                if (value != null) {
                    props.put(name, GroupUtil.toObject(value, clazz));
                }
            }
        }

    }

    /**
     * Extract the custom site properties from the specified {@link SlingHttpServletRequest} into the specified.
     * {@link Map}
     * @param request SlingHttpServletRequest
     * @param props map of values
     * @param session Session
     * @throws RepositoryException - thrown if unable to get props.
     * @throws OperationException - thrown if unable to get props.
     */
    protected void getCustomProperties(final SlingHttpServletRequest request, final Map props,
        final Session session) throws RepositoryException, OperationException {
        // TODO
    }

    private void addAllowedTemplate(final String path, final List allowedTemplates,
        final ResourceResolver resolver) {
        final Resource res = resolver.resolve(path);
        addAllowedTemplate(res, allowedTemplates, resolver);
    }

    private void addAllowedTemplate(final Resource res, final List allowedTemplates,
        final ResourceResolver resolver) {
        if (res != null && !ResourceUtil.isNonExistingResource(res)) {
            final ValueMap v = res.adaptTo(ValueMap.class);
            final boolean isDisabled = v.get(CommunityFunction.PROPERTY_BLUEPRINT_ENABLED, false);
            if (!isDisabled) {
                final Map data = new HashMap();
                data.put(PATH, res.getPath());
                data.put(NAME, v.get("jcr:title"));
                allowedTemplates.add(data);
            }
        }
    }

    private void addTemplatesUnderResource(final String path, final List allowedTemplates,
        final ResourceResolver userAdminResolver) {
        Iterator resourceIterator = null;
        if (path.startsWith("/")) {
            final Resource templates = userAdminResolver.resolve(path);
            if (templates != null && templates.hasChildren()) {
                resourceIterator = templates.getChildren().iterator();
            }
        } else {
            Resource resource = userAdminResolver.getResource(CUSTOM_GROUP_TEMPLATE_ROOT_LIBS);
            Conf confMgr = resource.adaptTo(Conf.class);
            List list = confMgr.getListResources(path);
            if (list != null) {
                resourceIterator = list.iterator();
            }
        }
        if (resourceIterator != null) {
            while (resourceIterator.hasNext()) {
                final Resource res = resourceIterator.next();
                if (res != null && !ResourceUtil.isNonExistingResource(res)) {
                    final ValueMap v = res.adaptTo(ValueMap.class);
                    final Map data = new HashMap();
                    data.put(PATH, res.getPath());
                    data.put(NAME, v.get("jcr:title"));
                    allowedTemplates.add(data);
                }
            }
        }
    }

    @Override
    public List getAllowedTemplateForEveryone(final String[] paths) {
        final List allowedTemplates = new ArrayList();
        ResourceResolver msmResolver = null;
        try {
            msmResolver = getServiceUserWrapper().getServiceResourceResolver(getBundleLocalResourceResolverFactory(),
                Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object) MSM_SERVICE));
            if (paths != null && paths.length > 0) {
                for (int i = 0; i < paths.length; i++) {
                    addAllowedTemplate(paths[i], allowedTemplates, msmResolver);
                }
            } else {
                if (msmResolver.getResource(DEFAULT_GROUP_TEMPLATE_ROOT) != null) {
                    // for backcomp
                    addTemplatesUnderResource(CUSTOM_GROUP_TEMPLATE_ROOT, allowedTemplates, msmResolver);
                    addTemplatesUnderResource(DEFAULT_GROUP_TEMPLATE_ROOT, allowedTemplates, msmResolver);
                } else {
                    addTemplatesUnderResource(DEFAULT_GROUP_ROOTTEMPLATE_RELATIVIEPATH_ROOT, allowedTemplates,
                        msmResolver);
                }
            }
        } catch (final LoginException e) {
            LOG.error("Failed to login as user admin.", e);
        } finally {
            if (msmResolver != null) {
                msmResolver.close();
            }
        }
        return allowedTemplates;
    }

    private CommunityGroupCollection getCommunityGroupCollectionForResource(final Resource groupCollection,
        final SlingHttpServletRequest request) {
        String path = groupCollection.getPath();
        if (!path.endsWith(CommunityGroupConstants.CONFIG_NODE_NAME)) {
            path = path + "/" + CommunityGroupConstants.CONFIG_NODE_NAME;
        }
        final Resource resource = request.getResourceResolver().getResource(path);
        if (resource == null) {
            LOG.debug("getCommunityGroupCollectionForResource: Resource at {} not found with resolver for user {}",
                path, request.getResourceResolver().getUserID());
            return null;
        }
        final SocialComponentFactory factory = componentFactoryManager.getSocialComponentFactory(resource);
        if (factory == null) {
            LOG.debug("getCommunityGroupCollectionForResource: SocialComponentFactory responsible for {} not found.",
                resource.getPath());
            return null;
        }
        final CommunityGroupCollection sc = (CommunityGroupCollection) factory.getSocialComponent(resource, request);
        if (sc == null) {
            LOG.debug("getCommunityGroupCollectionForResource: SocialComponentFactory {} returned null for {}.", sc,
                resource.getPath());
        }
        return sc;
    }

    /**
     * Get the SocialComponent for the specified {@link Resource} and {@link SlingHttpServletRequest}.
     * @param communityGroup the target community group
     * @param request the client request
     * @return the {@link SocialComponent}
     */
    @Override
    public SocialComponent getCommunityGroupComponentForResource(final Resource communityGroup,
        final SlingHttpServletRequest request) {
        String path = communityGroup.getPath();
        if (!path.endsWith(CommunityGroupConstants.CONFIG_NODE_NAME)) {
            path = path + "/" + CommunityGroupConstants.CONFIG_NODE_NAME;
        }
        final Resource resource = request.getResourceResolver().getResource(path);
        if (resource == null) {
            LOG.debug("getCommunityGroupComponentForResource: Resource at {} not found with resolver for user {}",
                path, request.getResourceResolver().getUserID());
            return null;
        }
        final SocialComponentFactory factory = componentFactoryManager.getSocialComponentFactory(resource);
        if (factory == null) {
            LOG.debug("getCommunityGroupComponentForResource: SocialComponentFactory responsible for {} not found.",
                resource.getPath());
            return null;
        }
        final SocialComponent sc = factory.getSocialComponent(resource, request);
        if (sc == null) {
            LOG.debug("getCommunityGroupComponentForResource: SocialComponentFactory {} returned null for {}.", sc,
                resource.getPath());
        }
        return sc;
    }

    /**
     * Get the SocialComponent for the specified {@link Resource} and {@link SlingHttpServletRequest}.
     * @param communityMembers the target community member list
     * @param request the client request
     * @return the {@link SocialComponent}
     */
    @Override
    public SocialComponent getCommunityMemberListComponentForResource(final Resource communityMembers,
        final SlingHttpServletRequest request) {
        final String path = communityMembers.getPath();
        final Resource resource = request.getResourceResolver().getResource(path);
        if (resource == null) {
            return null;
        }
        final SocialComponentFactory factory = componentFactoryManager.getSocialComponentFactory(resource);
        return (factory != null) ? factory.getSocialComponent(resource, request) : null;
    }

    protected CommunitySiteService getCommunitySiteService() {
        return siteService;
    }

    @Override
    protected SiteActivationService getActivationService() {
        return activationService;
    }

    @Override
    protected CommunityGroup getSocialComponent(Resource resource, SlingHttpServletRequest request) {
        return (CommunityGroup) getCommunityGroupComponentForResource(resource, request);
    }

    /**
     * Activate this component. Open the session and register event listeners.
     * @param context The component context
     */
    @Activate
    protected void activate(final ComponentContext context) {
        fieldWhitelist = OsgiUtil.toStringArray(context.getProperties().get(PROPERTY_FIELD_WHITELIST));
    }

    protected abstract U getCreateOperation();

    protected abstract U getUpdateOperation();

    protected abstract U getJoinOperation();

    protected abstract U getLeaveOperation();

}