com.ikanow.infinit.e.api.social.community.CommunityHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.ikanow.infinit.e.api.social.community.CommunityHandler.java

Source

/*******************************************************************************
 * Copyright 2012, The Infinit.e Open Source Project.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package com.ikanow.infinit.e.api.social.community;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.List;

import org.apache.log4j.Logger;
import org.bson.types.ObjectId;

import com.ikanow.infinit.e.api.config.source.SourceHandler;
import com.ikanow.infinit.e.api.custom.mapreduce.CustomHandler;
import com.ikanow.infinit.e.api.utils.SocialUtils;
import com.ikanow.infinit.e.api.utils.PropertiesManager;
import com.ikanow.infinit.e.api.utils.RESTTools;
import com.ikanow.infinit.e.data_model.Globals;
import com.ikanow.infinit.e.data_model.utils.SendMail;
import com.ikanow.infinit.e.data_model.api.ApiManager;
import com.ikanow.infinit.e.data_model.api.BasePojoApiMap;
import com.ikanow.infinit.e.data_model.api.ResponsePojo;
import com.ikanow.infinit.e.data_model.api.ResponsePojo.ResponseObject;
import com.ikanow.infinit.e.data_model.api.social.community.CommunityApprovalPojo;
import com.ikanow.infinit.e.data_model.api.social.community.CommunityPojoApiMap;
import com.ikanow.infinit.e.data_model.store.DbManager;
import com.ikanow.infinit.e.data_model.store.MongoDbManager;
import com.ikanow.infinit.e.data_model.store.config.source.SourcePojo;
import com.ikanow.infinit.e.data_model.store.custom.mapreduce.CustomMapReduceJobPojo;
import com.ikanow.infinit.e.data_model.store.document.DocCountPojo;
import com.ikanow.infinit.e.data_model.store.social.community.CommunityApprovePojo;
import com.ikanow.infinit.e.data_model.store.social.community.CommunityAttributePojo;
import com.ikanow.infinit.e.data_model.store.social.community.CommunityMemberContactPojo;
import com.ikanow.infinit.e.data_model.store.social.community.CommunityMemberLinkPojo;
import com.ikanow.infinit.e.data_model.store.social.community.CommunityMemberPojo;
import com.ikanow.infinit.e.data_model.store.social.community.CommunityMemberPojo.MemberType;
import com.ikanow.infinit.e.data_model.store.social.community.CommunityMemberUserAttributePojo;
import com.ikanow.infinit.e.data_model.store.social.community.CommunityPojo;
import com.ikanow.infinit.e.data_model.store.social.community.CommunityUserAttributePojo;
import com.ikanow.infinit.e.data_model.store.social.community.CommunityPojo.CommunityType;
import com.ikanow.infinit.e.data_model.store.social.person.PersonCommunityPojo;
import com.ikanow.infinit.e.data_model.store.social.person.PersonContactPojo;
import com.ikanow.infinit.e.data_model.store.social.person.PersonLinkPojo;
import com.ikanow.infinit.e.data_model.store.social.person.PersonPojo;
import com.ikanow.infinit.e.data_model.store.social.sharing.SharePojo;
import com.ikanow.infinit.e.data_model.store.social.sharing.SharePojo.ShareCommunityPojo;
import com.ikanow.infinit.e.data_model.store.social.sharing.SharePojo.ShareOwnerPojo;
import com.ikanow.infinit.e.processing.generic.GenericProcessingController;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;

/**
 * This class is for all operations related to the retrieval, addition
 * or update of communities within the system
 */
public class CommunityHandler {
    private static final Logger logger = Logger.getLogger(CommunityHandler.class);
    private SourceHandler sourceHandler = new SourceHandler();

    //////////////////////////////////////////////////////////////////////////
    ////////////////////////   REST handlers  ////////////////////////////////
    //////////////////////////////////////////////////////////////////////////

    /**
     * getCommunities (REST)
     * Returns information for all communities
     * @return
     */
    public ResponsePojo getCommunities(String userIdStr, CommunityPojo.CommunityType communityType) {
        ResponsePojo rp = new ResponsePojo();

        /////////////////////////////////////////////////////////////////////////////////////////////////
        // Note: Only Sys Admins see all communities
        boolean isSysAdmin = RESTTools.adminLookup(userIdStr);

        try {
            if (isSysAdmin) {
                BasicDBObject query = new BasicDBObject();
                addCommunityTypeTerm(query, communityType);
                DBCursor dbc = DbManager.getSocial().getCommunity().find(query);

                if (dbc.hasNext()) {
                    List<CommunityPojo> communities = CommunityPojo.listFromDb(dbc, CommunityPojo.listType());
                    filterCommunityMembers(communities, isSysAdmin, userIdStr);
                    rp.setData(communities, new CommunityPojoApiMap());
                    rp.setResponse(
                            new ResponseObject("Community Info", true, "Community info returned successfully"));
                } else {
                    rp.setResponse(new ResponseObject("Community Info", true, "No communities returned"));
                }
            } else // Get all public communities and all private communities to which the user belongs
            {
                // Set up the query
                BasicDBObject queryTerm1 = new BasicDBObject("communityAttributes.isPublic.value", "true");
                BasicDBObject queryTerm2 = new BasicDBObject("members._id", new ObjectId(userIdStr));
                BasicDBObject queryTerm3 = new BasicDBObject("ownerId", new ObjectId(userIdStr));
                BasicDBObject query = new BasicDBObject(MongoDbManager.or_,
                        Arrays.asList(queryTerm1, queryTerm2, queryTerm3));
                addCommunityTypeTerm(query, communityType);

                DBCursor dbc = DbManager.getSocial().getCommunity().find(query);
                if (dbc.hasNext()) {
                    List<CommunityPojo> communities = CommunityPojo.listFromDb(dbc, CommunityPojo.listType());
                    filterCommunityMembers(communities, isSysAdmin, userIdStr);
                    //add personal community
                    DBObject dbo = DbManager.getSocial().getCommunity()
                            .findOne(new BasicDBObject("_id", new ObjectId(userIdStr)));
                    if (dbo != null) {
                        communities.add(CommunityPojo.fromDb(dbo, CommunityPojo.class));
                    }
                    rp.setData(communities, new CommunityPojoApiMap());
                    rp.setResponse(
                            new ResponseObject("Community Info", true, "Community info returned successfully"));
                } else {
                    rp.setResponse(new ResponseObject("Community Info", true, "No communities returned"));
                }
            }
        } catch (Exception e) {
            logger.error("Exception Message: " + e.getMessage(), e);
            rp.setResponse(new ResponseObject("Community Info", false, "error returning community info"));
        }

        return rp;
    }

    /**
     * getCommunities (REST)
     * Returns information for communities where isPublic = true/false
     * @param isPublic
     * @return
     */
    public ResponsePojo getCommunities(String userIdStr, Boolean isPublic,
            CommunityPojo.CommunityType communityType) {
        ResponsePojo rp = new ResponsePojo();

        /////////////////////////////////////////////////////////////////////////////////////////////////
        // Note: Only Sys Admins see private communities
        boolean isSysAdmin = RESTTools.adminLookup(userIdStr);

        try {
            // Set up the query
            BasicDBObject query = new BasicDBObject();
            query.put("communityAttributes.isPublic.value", isPublic.toString());
            if (!isPublic && !isSysAdmin) {
                //Add user ID to query so only get (private) communities of which I'm a member
                query.put("members._id", new ObjectId(userIdStr));
            }
            query.put("communityStatus", new BasicDBObject("$ne", "disabled"));
            addCommunityTypeTerm(query, communityType);

            DBCursor dbc = DbManager.getSocial().getCommunity().find(query);
            if (dbc.hasNext()) {
                List<CommunityPojo> communities = CommunityPojo.listFromDb(dbc, CommunityPojo.listType());
                filterCommunityMembers(communities, isSysAdmin, userIdStr);
                rp.setData(communities, new CommunityPojoApiMap());
                rp.setResponse(new ResponseObject("Community Info", true, "Community info returned successfully"));
            } else {
                rp.setResponse(new ResponseObject("Community Info", true, "No communities returned"));
            }
        } catch (Exception e) {
            logger.error("Exception Message: " + e.getMessage(), e);
            rp.setResponse(new ResponseObject("Community Info", false, "error returning community info"));
        }
        return rp;
    }

    /**
     * getCommunity (REST)
     * Returns information for a single community
     * @param communityIdStr
     * @return
     */
    public ResponsePojo getCommunity(String userIdStr, String communityIdStr, boolean showDocInfo,
            CommunityPojo.CommunityType communityType) {
        ResponsePojo rp = new ResponsePojo();

        try {

            // Set up the query
            BasicDBObject query = new BasicDBObject();
            if (communityIdStr != null) {
                communityIdStr = allowCommunityRegex(userIdStr, communityIdStr);
                query.put("_id", new ObjectId(communityIdStr));
            } else {
                query.put("_id", new ObjectId("4c927585d591d31d7b37097a")); // (hardwired sys community)
            }
            addCommunityTypeTerm(query, communityType);

            // Get GsonBuilder object with MongoDb de/serializers registered
            BasicDBObject dbo = (BasicDBObject) DbManager.getSocial().getCommunity().findOne(query);

            if (dbo != null) {
                CommunityPojo community = CommunityPojo.fromDb(dbo, CommunityPojo.class);
                if (showDocInfo) {
                    DocCountPojo dc = (DocCountPojo) DbManager.getDocument().getCounts().findOne(query);
                    if (null != dc) {
                        dc.set_id(null);
                        community.setDocumentInfo(dc);
                    }
                }
                community = filterCommunityMembers(community, RESTTools.adminLookup(userIdStr), userIdStr);
                rp.setData(community, new CommunityPojoApiMap());
                rp.setResponse(new ResponseObject("Community Info", true, "Community info returned successfully"));
            } else {
                rp.setResponse(new ResponseObject("Community Info", false,
                        "Unable to return information for the community specified."));
            }
        } catch (Exception e) {
            //logger.error("Exception Message: " + e.getMessage(), e);
            rp.setResponse(new ResponseObject("Community Info", false,
                    "Error returning community info: " + e.getMessage()));
        }
        return rp;
    }

    /**
     * getSystemCommunity (REST)
     * @return ResponsePojo
     */
    public ResponsePojo getSystemCommunity(CommunityPojo.CommunityType communityType) {
        return getCommunity(null, null, false, communityType);
    }

    /**
     * addCommunity (REST)
     * Creates a new community
     * @param userIdStr
     * @param name
     * @param description
     * @param parentIdStr
     * @param parentName
     * @param tags
     * @param ownerId
     * @param ownerDisplayName
     * @return ResponsePojo
     */
    public ResponsePojo addCommunity(String userIdStr, String name, String description, String parentIdStr,
            String tags, CommunityPojo.CommunityType communityType) {
        String userName = null;
        String userEmail = null;
        String parentName = null;

        try {
            DBObject dboperson = DbManager.getSocial().getPerson()
                    .findOne(new BasicDBObject("_id", new ObjectId(userIdStr)));

            if (dboperson != null) {
                PersonPojo person = PersonPojo.fromDb(dboperson, PersonPojo.class);
                userName = person.getDisplayName();
                userEmail = person.getEmail();

                // Parent Community is Optional 
                if (parentIdStr != null) {
                    try {
                        DBObject dboparent = DbManager.getSocial().getCommunity()
                                .findOne(new BasicDBObject("_id", new ObjectId(parentIdStr)));
                        if (dboparent != null) {
                            CommunityPojo cp = CommunityPojo.fromDb(dboparent, CommunityPojo.class);
                            parentName = cp.getName();

                            if (cp.getIsPersonalCommunity()) {
                                return new ResponsePojo(new ResponseObject("Add Community", false,
                                        "Can't create sub-community of personal community"));
                            } //TESTED
                            if ((null == cp.getCommunityStatus())
                                    || !cp.getCommunityStatus().equalsIgnoreCase("active")) {
                                return new ResponsePojo(new ResponseObject("Add Community", false,
                                        "Can't create sub-community of inactive community"));
                            } //TESTED
                              // Check attributes
                            if (null != cp.getCommunityAttributes()) {
                                CommunityAttributePojo attr = cp.getCommunityAttributes()
                                        .get("usersCanCreateSubCommunities");
                                if ((null == attr) || (null == attr.getValue())
                                        || (attr.getValue().equals("false"))) {
                                    if (!cp.isOwner(person.get_id()) && !SocialUtils.isModerator(userIdStr, cp)
                                            && !RESTTools.adminLookup(userIdStr)) {
                                        return new ResponsePojo(new ResponseObject("Add Community", false,
                                                "Can't create sub-community when not permitted by parent"));
                                    } //TESTED (owner+admin+mod)
                                }
                            }
                        } //TESTED - different restrictions as above 
                        else {
                            return new ResponsePojo(
                                    new ResponseObject("Add Community", false, "Parent community does not exist"));
                        } //TESTED
                    } catch (Exception e) {
                        return new ResponsePojo(
                                new ResponseObject("Add Community", false, "Invalid parent community id"));
                    } //TESTED
                }
            } else {
                return new ResponsePojo(
                        new ResponseObject("Add Community", false, "Error: Unable to get person record"));
            }
        } catch (Exception ex) {
            return new ResponsePojo(
                    new ResponseObject("Add Community", false, "General Error: " + ex.getMessage()));
        }
        return addCommunity(userIdStr, name, description, parentIdStr, parentName, tags, userIdStr, userName,
                userEmail, communityType);
    }

    /**
     * addCommunity (REST)
     * Creates a new community by id (alternate)
     */
    private ResponsePojo addCommunity(String userIdStr, String name, String description, String parentIdStr,
            String parentName, String tags, String ownerIdStr, String ownerDisplayName, String ownerEmail,
            CommunityPojo.CommunityType communityType) {
        return addCommunity(userIdStr, null, name, description, parentIdStr, parentName, tags, ownerIdStr,
                ownerDisplayName, ownerEmail, communityType);
    }

    public ResponsePojo addCommunity(String userId, String idStr, String name, String description,
            String parentIdStr, String parentName, String tags, String ownerIdStr, String ownerDisplayName,
            String ownerEmail, CommunityPojo.CommunityType communityType) {
        ResponsePojo rp = new ResponsePojo();

        try {
            // Check to see if a community exists already with the supplied name or ID
            // do not create new one if true - 
            // TODO (INF-1214): Think about need for unique names - proposed:
            //        Community names unique per parent community
            BasicDBObject query = new BasicDBObject();
            if (idStr == null) {
                query.put("name", name);
            } else {
                query.put("_id", new ObjectId(idStr));
            }
            DBObject dbo = DbManager.getSocial().getCommunity().findOne(query);

            if (dbo == null) {
                ObjectId oId = null;
                if (idStr == null) {
                    oId = new ObjectId();
                } else {
                    oId = new ObjectId(idStr);
                }
                CommunityPojo c = new CommunityPojo();
                c.setType(communityType);
                c.setId(oId);
                c.setCreated(new Date());
                c.setModified(new Date());
                c.setName(name);
                c.setDescription(description);
                if (parentIdStr != null && parentName != null) {
                    c.setParentId(new ObjectId(parentIdStr));
                    c.setParentName(parentName);
                    c = SocialUtils.createParentTreeRecursion(c, false);
                }
                c.setIsPersonalCommunity(false);
                c.setTags(getTagsFromString(tags));
                c.setOwnerId(new ObjectId(ownerIdStr));
                c.setOwnerDisplayName(ownerDisplayName);
                c.setCommunityAttributes(getDefaultCommunityAttributes());
                c.setCommunityUserAttribute(getDefaultCommunityUserAttributes());

                // Insert new community document in the community collection
                DBObject commObj = c.toDb();

                // Create the index form of the community:
                if (CommunityType.user != c.getType()) {
                    try {
                        GenericProcessingController.createCommunityDocIndex(c.getId().toString(), c.getParentId(),
                                c.getIsPersonalCommunity(), c.getIsSystemCommunity(), false);
                    } catch (Exception e) { // Can't create community
                        rp.setResponse(new ResponseObject("Add Community", false,
                                "Error adding new community because of index failure: " + e.getMessage()));
                        return rp;
                    }
                }
                //TESTED

                DbManager.getSocial().getCommunity().save(commObj);

                // If a child, update the parent:
                if (null != c.getParentId()) {
                    BasicDBObject updateQuery = new BasicDBObject("_id", c.getParentId());
                    BasicDBObject updateUpdate = new BasicDBObject(DbManager.addToSet_,
                            new BasicDBObject("children", c.getId()));
                    DbManager.getSocial().getCommunity().update(updateQuery, updateUpdate, false, true);
                }
                //TESTED

                // Update the new community record to add the owner to the list of members
                rp = addCommunityMember(userId, oId.toStringMongod(), name, ownerIdStr, ownerEmail,
                        ownerDisplayName, "owner", "active");
                rp.setResponse(
                        new ResponseObject("Add Community", true, "The " + name + " community has been added."));
            } else {
                rp.setResponse(new ResponseObject("Add Community", false,
                        "Error adding new community. A community with the name " + name + " already exists."));
            }

        } catch (Exception e) {
            // If an exception occurs log the error
            logger.error("Exception Message: " + e.getMessage(), e);
            rp.setResponse(
                    new ResponseObject("Add Community", false, "Error adding new community " + e.getMessage()));
        }
        return rp;
    }

    /**
     * removeCommunity (REST)
     * Remove communityid only if the personid is the owner
     * TODO (INF-1214): Remove users from the groups in both personpojo and communitypojo 
     * 
     * @param personIdStr
     * @param communityIdStr
     * @return
     */
    public ResponsePojo removeCommunity(String personIdStr, String communityIdStr,
            CommunityPojo.CommunityType communityType) {
        boolean isSysAdmin = RESTTools.adminLookup(personIdStr);
        ResponsePojo rp = new ResponsePojo();
        try {
            ObjectId communityId = new ObjectId(communityIdStr);
            //get the communitypojo
            communityIdStr = allowCommunityRegex(personIdStr, communityIdStr);
            DBObject communitydbo = DbManager.getSocial().getCommunity()
                    .findOne(new BasicDBObject("_id", communityId));
            if (communitydbo != null) {
                CommunityPojo cp = CommunityPojo.fromDb(communitydbo, CommunityPojo.class);
                //get the personpojo
                DBObject persondbo = DbManager.getSocial().getPerson()
                        .findOne(new BasicDBObject("_id", new ObjectId(personIdStr)));
                if (persondbo != null) {
                    //PersonPojo pp = gson.fromJson(persondbo.toString(),PersonPojo.class);
                    if (!cp.getIsPersonalCommunity()) {
                        if (cp.isOwner(new ObjectId(personIdStr)) || isSysAdmin) {
                            if (cp.getCommunityStatus().equals("disabled")) { // Delete for good, this is going to be ugly...

                                if ((null != cp.getChildren()) && !cp.getChildren().isEmpty()) {
                                    rp.setResponse(new ResponseObject("Delete community", false,
                                            "Undeleted sub-communities exist, please delete them first"));
                                    return rp;
                                }
                                //TESTED

                                // 0] If it's a user group then remove from all communities

                                if (CommunityType.user == cp.getType()) {
                                    BasicDBObject query = new BasicDBObject("members._id", cp.getId());
                                    CommunityMemberPojo cmp = new CommunityMemberPojo();
                                    cmp.set_id(cp.getId());
                                    BasicDBObject actions = new BasicDBObject();
                                    actions.put("$pull",
                                            new BasicDBObject("members", new BasicDBObject("_id", cp.getId())));
                                    // ie for communities for which he's a member...remove...any elements of the list members...with his _id

                                    DbManager.getSocial().getCommunity().update(query, actions, false, true);
                                    // (don't upsert, many times)         
                                } //TESTED

                                // 1] Remove from all shares (delete shares if that leaves them orphaned)

                                BasicDBObject deleteQuery1 = new BasicDBObject(ShareCommunityPojo.shareQuery_id_,
                                        communityId);
                                BasicDBObject deleteFields1 = new BasicDBObject(SharePojo.communities_, 1);
                                List<SharePojo> shares = SharePojo.listFromDb(
                                        DbManager.getSocial().getShare().find(deleteQuery1, deleteFields1),
                                        SharePojo.listType());
                                for (SharePojo share : shares) {
                                    if (1 == share.getCommunities().size()) { // delete this share
                                        DbManager.getSocial().getShare()
                                                .remove(new BasicDBObject(SharePojo._id_, share.get_id()));
                                    }
                                }
                                BasicDBObject update1 = new BasicDBObject(DbManager.pull_,
                                        new BasicDBObject(SharePojo.communities_,
                                                new BasicDBObject(ShareOwnerPojo._id_, communityId)));
                                DbManager.getSocial().getShare().update(deleteQuery1, update1, false, true);

                                //TESTED (both types)

                                // 2] Remove from all sources (also delete the documents)
                                // (In most cases this will leave the source orphaned, so delete it)

                                BasicDBObject deleteQuery2 = new BasicDBObject(SourcePojo.communityIds_,
                                        communityId);
                                BasicDBObject deleteFields2 = new BasicDBObject(SourcePojo.communityIds_, 1);
                                List<SourcePojo> sources = SourcePojo.listFromDb(
                                        DbManager.getIngest().getSource().find(deleteQuery2, deleteFields2),
                                        SourcePojo.listType());
                                List<SourcePojo> failedSources = new ArrayList<SourcePojo>();
                                for (SourcePojo source : sources) {
                                    ResponsePojo rp1 = null;
                                    SourceHandler tmpHandler = new SourceHandler();
                                    if (1 == source.getCommunityIds().size()) { // delete this source
                                        rp1 = tmpHandler.deleteSource(source.getId().toString(), communityIdStr,
                                                personIdStr, false);
                                        // (deletes all docs and removes from the share)
                                    } else { // Still need to delete docs from this share from this community
                                        rp1 = tmpHandler.deleteSource(source.getId().toString(), communityIdStr,
                                                personIdStr, true);
                                    }
                                    if (rp1 != null && !rp1.getResponse().isSuccess()) {
                                        failedSources.add(source);
                                    }
                                }

                                //if we have more than 1 failed source, bail out w/ error
                                if (failedSources.size() > 0) {
                                    StringBuilder sb = new StringBuilder();
                                    for (SourcePojo source : failedSources)
                                        sb.append(source.getId().toString() + " ");
                                    rp.setResponse(new ResponseObject("Delete community", false,
                                            "Could not stop sources (they might be currently running): "
                                                    + sb.toString()));
                                    return rp;
                                }

                                BasicDBObject update2 = new BasicDBObject(DbManager.pull_,
                                        new BasicDBObject(SourcePojo.communityIds_, communityId));
                                DbManager.getSocial().getShare().update(deleteQuery2, update2, false, true);

                                //TESTED (both types, check docs deleted)

                                // 3] Remove from all map reduce jobs (delete any that it is only comm left on)
                                String customJobsMessage = removeCommunityFromJobs(personIdStr, communityId);
                                if (customJobsMessage.length() > 0) {
                                    rp.setResponse(new ResponseObject("Delete community", false,
                                            "Could not stop all map reduce jobs (they might be currently running): "
                                                    + customJobsMessage));
                                    return rp;
                                }

                                // 4] Finally delete the object itself

                                DbManager.getSocial().getCommunity().remove(new BasicDBObject("_id", communityId));

                                // Remove from index:
                                if (CommunityType.user != cp.getType()) {
                                    GenericProcessingController.deleteCommunityDocIndex(communityId.toString(),
                                            cp.getParentId(), false);
                                }
                                //TESTED

                                // 5] Finally finally remove from parent communities
                                if (null != cp.getParentId()) {
                                    BasicDBObject updateQuery = new BasicDBObject("_id", cp.getParentId());
                                    BasicDBObject updateUpdate = new BasicDBObject(DbManager.pull_,
                                            new BasicDBObject("children", cp.getId()));
                                    DbManager.getSocial().getCommunity().update(updateQuery, updateUpdate, false,
                                            true);
                                }
                                //TESTED

                                rp.setResponse(new ResponseObject("Delete community", true,
                                        "Community deleted forever. " + customJobsMessage));
                            } else { // First time, just remove all users and disable
                                //at this point, we have verified, community/user exist, not a personal group, user is member and owner
                                //set community as inactive (for some reason we don't delete it)
                                DbManager.getSocial().getCommunity().update(new BasicDBObject("_id", communityId),
                                        new BasicDBObject(DbManager.set_,
                                                new BasicDBObject("communityStatus", "disabled")));

                                //remove all members
                                for (CommunityMemberPojo cmp : cp.getMembers())
                                    removeCommunityMember(personIdStr, communityIdStr, cmp.get_id().toString());
                                rp.setResponse(new ResponseObject("Delete community", true,
                                        "Community disabled successfully - call delete again to remove for good, including all sources, shares, and documents"));
                            }
                        } else {
                            rp.setResponse(new ResponseObject("Delete community", false,
                                    "You are not the owner of this community"));
                        }
                    } else {
                        rp.setResponse(
                                new ResponseObject("Delete community", false, "Cannot delete personal community."));
                    }
                } else {
                    rp.setResponse(new ResponseObject("Delete community", false,
                            "Person ID was incorrect, no matching person found"));
                }
            } else {
                rp.setResponse(new ResponseObject("Delete community", false,
                        "Community ID was incorrect, no matching commmunity found"));
            }

        } catch (Exception ex) {
            rp.setResponse(new ResponseObject("Delete community", false,
                    "Error returning community info: " + ex.getMessage()));
        }

        return rp;
    }

    /**
     * Removes this community from any map reduce jobs, then deletes any jobs where it was the only community
     * 
     * @param communityId
     * @return 
     */
    private String removeCommunityFromJobs(String personIdStr, ObjectId communityId) {
        StringBuilder sb = new StringBuilder();
        List<CustomMapReduceJobPojo> failedToRemove = CustomHandler.removeCommunityJobs(communityId);
        //return a list of job ids
        for (CustomMapReduceJobPojo cmr : failedToRemove) {
            sb.append(cmr.jobtitle + " ");
        }
        if (sb.length() > 0) {
            sb.insert(0, "These map reduce jobs could not be removed: ");
        }
        return sb.toString();
    }

    // (Note supports personId as either Id or username (email) both are unique indexes)

    /**
     * updateMemberStatus (REST)
     */

    public ResponsePojo updateMemberStatus(String callerIdStr, String personIdStr, String communityIdStr,
            String userStatus, CommunityPojo.CommunityType communityType) {
        boolean isSysAdmin = RESTTools.adminLookup(callerIdStr);
        ResponsePojo rp = new ResponsePojo();
        try {
            communityIdStr = allowCommunityRegex(callerIdStr, communityIdStr);

            //verify user is in this community, then update status
            BasicDBObject query = new BasicDBObject("_id", new ObjectId(communityIdStr));
            DBObject dbo = DbManager.getSocial().getCommunity().findOne(query);
            if (dbo != null) {
                // PersonId can be _id or username/email
                BasicDBObject dboPerson = (BasicDBObject) DbManager.getSocial().getPerson()
                        .findOne(new BasicDBObject("email", personIdStr));
                if (null != dboPerson) { // (ie personId isn't an email address... convert to ObjectId and try again)
                    personIdStr = dboPerson.getString("_id");
                }
                // OK from here on, personId is the object Id...

                boolean bAuthorized = isSysAdmin || SocialUtils.isOwnerOrModerator(communityIdStr, callerIdStr)
                        || isRemovingSelf(userStatus, callerIdStr, personIdStr);
                if (bAuthorized) {

                    CommunityPojo cp = CommunityPojo.fromDb(dbo, CommunityPojo.class);
                    ObjectId personId = new ObjectId(personIdStr);

                    if (cp.isOwner(personId) && !userStatus.equalsIgnoreCase("active")) {
                        rp.setResponse(new ResponseObject("Update member status", false,
                                "Can't change owner status, remove their ownership first"));
                        return rp;
                    } //TESTED (tried as owner+admin (failed both times), tested owner works fine if setting/keeping active)
                    else if (cp.isMember(personId)) {
                        // Remove user:
                        if (userStatus.equalsIgnoreCase("remove")) {
                            //removeCommunityMember(callerIdStr, communityIdStr, personIdStr);
                            rp = removeCommunityMember(callerIdStr, communityIdStr, personIdStr); //.setResponse(new ResponseObject("Update member status",true,"Member removed from community."));
                        } else {
                            //verified user, update status
                            if (cp.updateMemberStatus(personIdStr, userStatus)) {
                                /////////////////////////////////////////////////////////////////////////////////////////////////
                                // TODO (INF-1214): Make this code more robust to handle changes to the community that need to
                                // Caleb: this means change update to $set
                                /////////////////////////////////////////////////////////////////////////////////////////////////
                                DbManager.getSocial().getCommunity().update(query, cp.toDb());
                                rp.setResponse(new ResponseObject("Update member status", true,
                                        "Updated member status successfully"));
                            } else {
                                rp.setResponse(new ResponseObject("Update member status", false,
                                        "Failed to update status"));
                            }
                        }
                    } else {
                        rp.setResponse(new ResponseObject("Update member status", false,
                                "User was not a member of the community"));
                    }
                } else {
                    rp.setResponse(new ResponseObject("Update member status", false,
                            "Caller must be admin, or a community owner or moderator"));
                } //TESTED - tried to update my status as member (failed), as admin (succeeded), as moderator (succeeded), as owner (succeeded)  
            } else {
                rp.setResponse(new ResponseObject("Update member status", false, "Community does not exist"));
            }
        } catch (Exception ex) {
            rp.setResponse(new ResponseObject("Update member status", false, Globals
                    .populateStackTrace(new StringBuffer("General Error, bad params maybe? "), ex).toString()));
        }
        return rp;
    }//TESTED

    /**
     * Utility function for testing self removal from communities
     * Returns true if the status is "remove" and callerId == personId
     * 
     * @param userStatus
     * @param callerIdStr
     * @param personIdStr
     * @return
     */
    private boolean isRemovingSelf(String userStatus, String callerIdStr, String personIdStr) {
        return userStatus.toLowerCase().equals("remove") && (callerIdStr.equals(personIdStr));
    }

    // (Note supports personId as either Id or username (email) both are unique indexes)

    /**
     * bulkUpdateOperation (REST)
     * For ,-separated personIdStr variables, applies the operation to each member in turn, aggregating the results, supporting:
     * - inviteCommunity
     * - updateMemberType
     * - updateMemberStatus
     */
    public ResponsePojo bulkUpdateOperation(String opName, String callerIdStr, String personIdStr, String userType,
            String userStatus, String communityIdStr, CommunityPojo.CommunityType communityType,
            String skipInvitation) {
        String[] personIds = personIdStr.split("\\s*,\\s*");
        HashMap<String, String> failed = new HashMap<String, String>();
        int num_failed = 0;
        LinkedList<String> succeeded = new LinkedList<String>();
        int num_succeeded = 0;
        ResponsePojo rp = new ResponsePojo();
        rp.setResponse(new ResponseObject());
        rp.getResponse().setSuccess(true);
        for (String subPerson : personIds) {
            ResponsePojo tmpRp = null;
            if (opName.equalsIgnoreCase("inviteCommunity")) {
                tmpRp = this.inviteCommunity(callerIdStr, subPerson, communityIdStr, skipInvitation, communityType);
            } else if (opName.equalsIgnoreCase("updateMemberType")) {
                tmpRp = this.updateMemberType(callerIdStr, subPerson, communityIdStr, userType, communityType);
            } else if (opName.equalsIgnoreCase("updateMemberStatus")) {
                tmpRp = this.updateMemberStatus(callerIdStr, subPerson, communityIdStr, userStatus, communityType);
            }
            rp.getResponse().setAction(tmpRp.getResponse().getAction());
            rp.getResponse().setTime(rp.getResponse().getTime() + tmpRp.getResponse().getTime());
            if (!tmpRp.getResponse().isSuccess()) {
                rp.getResponse().setSuccess(false);
                failed.put(subPerson, tmpRp.getResponse().getMessage());
                num_failed++;
            } else {
                succeeded.add(subPerson);
                num_succeeded++;
            }
        } //(end loop over bulk users)
        BasicDBObject breakdown = new BasicDBObject();
        breakdown.put("succeeded", succeeded);
        breakdown.put("failed", failed);
        rp.getResponse().setMessage("succeeded=" + num_succeeded + " failed=" + num_failed);
        rp.setData(breakdown, (BasePojoApiMap<BasicDBObject>) null);

        return rp;

    }//TESTED (by hand)

    /**
     * updateMemberType (REST)
     */
    public ResponsePojo updateMemberType(String callerIdStr, String personIdStr, String communityIdStr,
            String userType, CommunityPojo.CommunityType communityType) {
        boolean isSysAdmin = RESTTools.adminLookup(callerIdStr);
        ResponsePojo rp = new ResponsePojo();
        try {
            if (!userType.equalsIgnoreCase("owner") && !userType.equalsIgnoreCase("moderator")
                    && !userType.equalsIgnoreCase("member") && !userType.equalsIgnoreCase("content_publisher")) {
                rp.setResponse(new ResponseObject("Update member type", false, "Invalid user type: " + userType));
                return rp;
            } //TESTED - tested all the types work, hacked members.jsp to insert invalid type

            //verify user is in this community, then update status
            communityIdStr = allowCommunityRegex(callerIdStr, communityIdStr);
            BasicDBObject query = new BasicDBObject("_id", new ObjectId(communityIdStr));
            DBObject dbo = DbManager.getSocial().getCommunity().findOne(query);
            if (dbo != null) {
                // PersonId can be _id or username/email
                BasicDBObject dboPerson = (BasicDBObject) DbManager.getSocial().getPerson()
                        .findOne(new BasicDBObject("email", personIdStr));
                if (null != dboPerson) { // (ie personId isn't an email address... convert to ObjectId and try again)
                    personIdStr = dboPerson.getString("_id");
                }
                // OK from here on, personId is the object Id...

                CommunityPojo cp = CommunityPojo.fromDb(dbo, CommunityPojo.class);

                boolean bOwnershipChangeRequested = userType.equalsIgnoreCase("owner");
                boolean bAuthorized = isSysAdmin;
                if (!bAuthorized) {
                    if (bOwnershipChangeRequested) { // must be owner or admin            
                        bAuthorized = cp.isOwner(new ObjectId(callerIdStr));
                    } //TESTED - tried to update myself as moderator to owner (failed), gave up my community (succeeded), changed ownership as admin (FAILED) 
                    else { // Can also be moderator
                        bAuthorized = SocialUtils.isOwnerOrModerator(communityIdStr, callerIdStr);
                    } //TESTED - tried to update my role as member (failed), as admin->moderator (succeeded), as moderator (succeeded)
                }

                if (bAuthorized) // (see above)
                {
                    if (cp.isMember(new ObjectId(personIdStr))) {
                        boolean bChangedMembership = false;
                        boolean bChangedOwnership = !bOwnershipChangeRequested;

                        ObjectId personId = new ObjectId(personIdStr);

                        // Check that not trying to downgrade owner...
                        if (cp.isOwner(personId) && !bOwnershipChangeRequested) {
                            rp.setResponse(new ResponseObject("Update member type", false,
                                    "To change ownership, set new owner, will automatically downgrade existing owner to moderator"));
                            return rp;
                        } //TESTED

                        String personDisplayName = null;
                        //verified user, update status
                        for (CommunityMemberPojo cmp : cp.getMembers()) {
                            if (cmp.get_id().equals(personId)) {
                                if ((MemberType.user_group == cmp.getType()) && bOwnershipChangeRequested) {
                                    // Not allowed to set a user group to be the owner (obv)
                                    rp.setResponse(new ResponseObject("Update member type", false,
                                            "Can't set a user group to be the owner"));
                                    return rp;
                                } //TESTED

                                cmp.setUserType(userType);
                                personDisplayName = cmp.getDisplayName();
                                bChangedMembership = true;

                                if (bChangedOwnership) { // (includes case where didn't need to)
                                    break;
                                }

                            } //TESTED 
                            if (bOwnershipChangeRequested && cmp.get_id().equals(cp.getOwnerId())) {
                                cmp.setUserType("moderator");
                                bChangedOwnership = true;

                                if (bChangedMembership) {
                                    break;
                                }

                            } //TESTED
                        }
                        if (bChangedMembership) {
                            if (bOwnershipChangeRequested) {
                                cp.setOwnerId(personId);
                                cp.setOwnerDisplayName(personDisplayName);
                            } //TESTED

                            /////////////////////////////////////////////////////////////////////////////////////////////////
                            // TODO (INF-1214): Make this code more robust to handle changes to the community that need to
                            // Caleb: this means change update to $set
                            /////////////////////////////////////////////////////////////////////////////////////////////////
                            DbManager.getSocial().getCommunity().update(query, cp.toDb());
                            rp.setResponse(new ResponseObject("Update member type", true,
                                    "Updated member type successfully"));
                        } //TESTED                           
                    } else {
                        rp.setResponse(new ResponseObject("Update member type", false,
                                "User was not a member of the community"));
                    }
                } else {
                    rp.setResponse(new ResponseObject("Update member type", false,
                            "Caller must be admin/owner/moderator (unless changing ownership)"));
                }
            } else {
                rp.setResponse(new ResponseObject("Update member type", false, "Community does not exist"));
            }
        } catch (Exception ex) {
            rp.setResponse(new ResponseObject("Update member type", false, Globals
                    .populateStackTrace(new StringBuffer("General Error, bad params maybe? "), ex).toString()));
        }
        return rp;
    }//TESTED (see sub-clauses for details)

    /**
     * joinCommunity (REST)
     */
    public ResponsePojo joinCommunity(String personIdStr, String communityIdStr,
            CommunityPojo.CommunityType communityType) {
        boolean isSysAdmin = RESTTools.adminLookup(personIdStr);
        return joinCommunity(personIdStr, communityIdStr, isSysAdmin, communityType);
    }

    public ResponsePojo joinCommunity(String personIdStr, String communityIdStr, boolean isSysAdmin,
            CommunityPojo.CommunityType communityType) {
        ResponsePojo rp = new ResponsePojo();
        try {
            communityIdStr = allowCommunityRegex(personIdStr, communityIdStr);
            BasicDBObject query = new BasicDBObject("_id", new ObjectId(communityIdStr));
            DBObject dboComm = DbManager.getSocial().getCommunity().findOne(query);
            if (dboComm != null) {
                CommunityPojo cp = CommunityPojo.fromDb(dboComm, CommunityPojo.class);
                if (!cp.getIsPersonalCommunity()) {
                    BasicDBObject queryPerson = new BasicDBObject("_id", new ObjectId(personIdStr));
                    DBObject dboPerson = DbManager.getSocial().getPerson().findOne(queryPerson);
                    ObjectId personOrUserGroupId = null;
                    PersonPojo pp = null;
                    CommunityPojo userGroup = null;
                    if (null == dboPerson) {
                        userGroup = CommunityPojo.fromDb(DbManager.getSocial().getCommunity()
                                .findOne(new BasicDBObject("_id", new ObjectId(personIdStr))), CommunityPojo.class);
                        if (null != userGroup)
                            personOrUserGroupId = userGroup.getId();

                        if ((null != userGroup) && (CommunityType.user != userGroup.getType())) {
                            rp.setResponse(new ResponseObject("Join member to community", false,
                                    "Can't add data groups as members."));
                            return rp;
                        }
                        if ((null != userGroup) && (CommunityType.user == cp.getType())) {
                            rp.setResponse(new ResponseObject("Join member to community", false,
                                    "Can't add user groups to user groups."));
                            return rp;
                        }
                    } else {
                        pp = PersonPojo.fromDb(dboPerson, PersonPojo.class);
                        if (null != pp)
                            personOrUserGroupId = pp.get_id();
                    }
                    if ((null == pp) && (null == userGroup)) {
                        rp.setResponse(
                                new ResponseObject("Join community", false, "Can't find user or user group."));
                        return rp;
                    }

                    boolean isPending = isMemberPending(cp, personOrUserGroupId);

                    if (!cp.isMember(new ObjectId(personIdStr)) || isPending) {
                        Map<String, CommunityAttributePojo> commatt = cp.getCommunityAttributes();
                        if (isSysAdmin || (commatt.containsKey("usersCanSelfRegister")
                                && commatt.get("usersCanSelfRegister").getValue().equals("true"))) {
                            boolean requiresApproval = false;
                            if (!isSysAdmin && commatt.containsKey("registrationRequiresApproval"))
                                requiresApproval = commatt.get("registrationRequiresApproval").getValue()
                                        .equals("true");
                            //if approval is required, add user to comm, wait for owner to approve
                            //otherwise go ahead and add as a member
                            if (requiresApproval) {
                                cp.addMember(personOrUserGroupId, pp, userGroup, true);
                                //write both objects back to db now
                                /////////////////////////////////////////////////////////////////////////////////////////////////
                                // TODO (INF-1214): Make this code more robust to handle changes to the community that need to
                                // Caleb: this means change update to $set
                                /////////////////////////////////////////////////////////////////////////////////////////////////
                                DbManager.getSocial().getCommunity().update(query, cp.toDb());

                                //send email out to owner for approval
                                CommunityApprovePojo cap = cp.createCommunityApprove(RESTTools.generateRandomId(),
                                        personIdStr, communityIdStr, "join", personIdStr);
                                DbManager.getSocial().getCommunityApprove().insert(cap.toDb());

                                // Get to addresses for Owner and Moderators
                                String toAddresses = getToAddressesFromCommunity(cp);

                                PropertiesManager propManager = new PropertiesManager();
                                String rootUrl = propManager.getUrlRoot();

                                String displayName = (null != pp) ? pp.getDisplayName() : userGroup.getName();

                                String subject = displayName + " is trying to join infinit.e community: "
                                        + cp.getName();
                                String body = displayName + " is trying to join infinit.e community: "
                                        + cp.getName() + "<br/>Do you want to accept this request?"
                                        + "<br/><a href=\"" + rootUrl + "social/community/requestresponse/"
                                        + cap.get_id().toString() + "/true\">Accept</a> " + "<a href=\"" + rootUrl
                                        + "social/community/requestresponse/" + cap.get_id().toString()
                                        + "/false\">Deny</a>";

                                SendMail mail = new SendMail(new PropertiesManager().getAdminEmailAddress(),
                                        toAddresses, subject, body);

                                if (mail.send("text/html")) {
                                    rp.setResponse(new ResponseObject("Join Community", true,
                                            "Joined community successfully, awaiting owner approval"));
                                    rp.setData(new CommunityApprovalPojo(false));
                                } else {
                                    rp.setResponse(new ResponseObject("Join Community", false,
                                            "The system was uable to send an email to the owner"));
                                }
                            } else {
                                cp.addMember(personOrUserGroupId, pp, userGroup);
                                if (null != pp)
                                    pp.addCommunity(cp);
                                //write both objects back to db now
                                /////////////////////////////////////////////////////////////////////////////////////////////////
                                // TODO (INF-1214): Make this code more robust to handle changes to the community that need to
                                // Caleb: this means change update to $set
                                /////////////////////////////////////////////////////////////////////////////////////////////////
                                DbManager.getSocial().getCommunity().update(query, cp.toDb());
                                if (null != pp)
                                    DbManager.getSocial().getPerson().update(queryPerson, pp.toDb());
                                rp.setResponse(new ResponseObject("Join Community", true,
                                        "Joined community successfully"));
                                rp.setData(new CommunityApprovalPojo(true));
                            }
                        } else {
                            rp.setResponse(new ResponseObject("Join Community", false,
                                    "You must be invited to this community"));
                        }
                    } else {
                        rp.setResponse(new ResponseObject("Join Community", false,
                                "You are already a member of this community"));
                    }
                } else {
                    rp.setResponse(new ResponseObject("Join Community", false,
                            "Cannot add members to personal community"));
                }
            } else {
                rp.setResponse(new ResponseObject("Join Community", false, "Community does not exist"));
            }
        } catch (Exception ex) {

            rp.setResponse(new ResponseObject("Join Community", false, Globals
                    .populateStackTrace(new StringBuffer("General Error, bad params maybe? "), ex).toString()));
        }
        return rp;
    }

    /**
     * leaveCommunity (REST)
     */

    /**/
    //TODO: can this be called for user groups?

    public ResponsePojo leaveCommunity(String personIdStr, String communityIdStr,
            CommunityPojo.CommunityType communityType) {
        ResponsePojo rp = new ResponsePojo();
        try {
            communityIdStr = allowCommunityRegex(personIdStr, communityIdStr);
            BasicDBObject query = new BasicDBObject("_id", new ObjectId(communityIdStr));
            DBObject dboComm = DbManager.getSocial().getCommunity().findOne(query);
            if (dboComm != null) {
                CommunityPojo cp = CommunityPojo.fromDb(dboComm, CommunityPojo.class);
                if (!cp.getIsPersonalCommunity()) {
                    BasicDBObject queryPerson = new BasicDBObject("_id", new ObjectId(personIdStr));
                    DBObject dboPerson = DbManager.getSocial().getPerson().findOne(queryPerson);
                    PersonPojo pp = PersonPojo.fromDb(dboPerson, PersonPojo.class);
                    cp.removeMember(new ObjectId(personIdStr));
                    pp.removeCommunity(cp);
                    //write both objects back to db now
                    /////////////////////////////////////////////////////////////////////////////////////////////////
                    // TODO (INF-1214): Make this code more robust to handle changes to the community that need to
                    // Caleb: this means change update to $set
                    /////////////////////////////////////////////////////////////////////////////////////////////////
                    DbManager.getSocial().getCommunity().update(query, cp.toDb());
                    DbManager.getSocial().getPerson().update(queryPerson, pp.toDb());
                    rp.setResponse(new ResponseObject("Leave Community", true, "Left community successfully"));
                } else {
                    rp.setResponse(new ResponseObject("Leave Community", false, "Cannot leave personal community"));
                }
            } else {
                rp.setResponse(new ResponseObject("Leave Community", false, "Community does not exist"));
            }
        } catch (Exception ex) {
            rp.setResponse(new ResponseObject("Leave Community", false, Globals
                    .populateStackTrace(new StringBuffer("General Error, bad params maybe? "), ex).toString()));
        }
        return rp;
    }

    /**
     * inviteCommunity (REST)
     * Invite a user to a community, only add them as pending to community
     * and do not add community into the person object yet
     * Need to send email out
    // (Note supports personId as either Id or username (email) both are unique indexes)
     * @param personIdStr
     * @param userIdStr
     * @param communityIdStr
     * @return
     */
    public ResponsePojo inviteCommunity(String userIdStr, String personIdStr, String communityIdStr,
            String skipInvitation, CommunityPojo.CommunityType communityType) {
        ResponsePojo rp = new ResponsePojo();
        try {
            communityIdStr = allowCommunityRegex(userIdStr, communityIdStr);
        } catch (Exception e) {
            rp.setResponse(new ResponseObject("Invite Community", false,
                    "Error returning community info: " + e.getMessage()));
            return rp;
        }

        boolean skipInvite = ((null != skipInvitation) && (skipInvitation.equalsIgnoreCase("true"))) ? true : false;

        /////////////////////////////////////////////////////////////////////////////////////////////////
        // Note: Only Sys Admins, Community Owner, and Community Moderators can invite users to
        // private communities however any member can be able to invite someone to a public community
        boolean isOwnerOrModerator = SocialUtils.isOwnerOrModerator(communityIdStr, userIdStr);
        boolean isSysAdmin = RESTTools.adminLookup(userIdStr);
        boolean canInvite = (isOwnerOrModerator || isSysAdmin) ? true : false;

        try {
            BasicDBObject query = new BasicDBObject("_id", new ObjectId(communityIdStr));
            DBObject dboComm = DbManager.getSocial().getCommunity().findOne(query);

            if (dboComm != null) {
                CommunityPojo cp = CommunityPojo.fromDb(dboComm, CommunityPojo.class);

                // Make sure this isn't a personal community
                if (!cp.getIsPersonalCommunity()) {
                    // Check to see if the user has permissions to invite or selfregister
                    boolean selfRegister = canSelfRegister(cp);
                    if (canInvite || cp.getOwnerId().toString().equalsIgnoreCase(userIdStr) || selfRegister) {
                        BasicDBObject dboPerson = (BasicDBObject) DbManager.getSocial().getPerson()
                                .findOne(new BasicDBObject("email", personIdStr));
                        if (null == dboPerson) { // (ie personId isn't an email address... convert to ObjectId and try again)
                            dboPerson = (BasicDBObject) DbManager.getSocial().getPerson()
                                    .findOne(new BasicDBObject("_id", new ObjectId(personIdStr)));
                        } else {
                            personIdStr = dboPerson.getString("_id");
                        }
                        ObjectId personOrUserGroupId = null;
                        PersonPojo pp = null;
                        CommunityPojo userGroup = null;
                        if (null == dboPerson) {
                            userGroup = CommunityPojo.fromDb(
                                    DbManager.getSocial().getCommunity()
                                            .findOne(new BasicDBObject("_id", new ObjectId(personIdStr))),
                                    CommunityPojo.class);
                            if (null != userGroup)
                                personOrUserGroupId = userGroup.getId();

                            if ((null != userGroup) && (CommunityType.user != userGroup.getType())) {
                                rp.setResponse(new ResponseObject("Join member to community", false,
                                        "Can't add data groups as members."));
                                return rp;
                            }
                            if ((null != userGroup) && (CommunityType.user == cp.getType())) {
                                rp.setResponse(new ResponseObject("Join member to community", false,
                                        "Can't add user groups to user groups."));
                                return rp;
                            }
                        } else {
                            pp = PersonPojo.fromDb(dboPerson, PersonPojo.class);
                            if (null != pp)
                                personOrUserGroupId = pp.get_id();
                        }
                        // OK from here on, personIdStr is the object Id...

                        if ((pp != null) || (userGroup != null)) {
                            //need to check for if a person is pending, and skipInvite and isSysAdmin, otherwise
                            //they would just get sent an email again, so leave it be
                            boolean isPending = false;
                            if (isSysAdmin && skipInvite) {
                                isPending = isMemberPending(cp, personOrUserGroupId);
                            }

                            if (selfRegister) {
                                //If the comm allows for self registering, just call join community
                                //instead of invite, it will handle registration
                                return this.joinCommunity(personOrUserGroupId.toString(), communityIdStr,
                                        isSysAdmin, communityType);
                            } else if (!cp.isMember(personOrUserGroupId) || isPending) {
                                if (isSysAdmin && skipInvite) // Can only skip invite if user is Admin
                                {
                                    // Update community with new member
                                    cp.addMember(personOrUserGroupId, pp, userGroup, false); // Member status set to Active
                                    /////////////////////////////////////////////////////////////////////////////////////////////////
                                    // TODO (INF-1214): Make this code more robust to handle changes to the community that need to
                                    // Caleb: this means change update to $set
                                    /////////////////////////////////////////////////////////////////////////////////////////////////
                                    DbManager.getSocial().getCommunity().update(query, cp.toDb());

                                    /////////////////////////////////////////////////////////////////////////////////////////////////
                                    // TODO (INF-1214): Make this code more robust to handle changes to the community that need to
                                    // Caleb: this means change update to $set
                                    /////////////////////////////////////////////////////////////////////////////////////////////////
                                    if (null != pp) {
                                        // Add community to persons object and save to db
                                        pp.addCommunity(cp);

                                        DbManager.getSocial().getPerson()
                                                .update(new BasicDBObject("_id", pp.get_id()), pp.toDb());
                                        rp.setResponse(new ResponseObject("Invite Community", true,
                                                "User added to community successfully."));
                                    } else {
                                        //TODO (INF-2866): might need to do some person manipulation
                                        rp.setResponse(new ResponseObject("Invite Community", true,
                                                "User group added to community successfully."));
                                    }
                                } else {
                                    cp.addMember(personOrUserGroupId, pp, userGroup, true); // Member status set to Pending
                                    /////////////////////////////////////////////////////////////////////////////////////////////////
                                    // TODO (INF-1214): Make this code more robust to handle changes to the community that need to
                                    // Caleb: this means change update to $set
                                    /////////////////////////////////////////////////////////////////////////////////////////////////
                                    DbManager.getSocial().getCommunity().update(query, cp.toDb());

                                    //send email out inviting user
                                    CommunityApprovePojo cap = cp.createCommunityApprove(
                                            RESTTools.generateRandomId(), personIdStr, communityIdStr, "invite",
                                            userIdStr);
                                    DbManager.getSocial().getCommunityApprove().insert(cap.toDb());

                                    PropertiesManager propManager = new PropertiesManager();
                                    String rootUrl = propManager.getUrlRoot();

                                    if (null == rootUrl) {
                                        rp.setResponse(new ResponseObject("Invite Community", false,
                                                "The system was unable to email the invite because an invite was required and root.url is not set up."));
                                        return rp;
                                    }

                                    String subject = "Invite to join infinit.e community: " + cp.getName();
                                    String body = "You have been invited to join the community " + cp.getName()
                                            + "<br/><a href=\"" + rootUrl + "social/community/requestresponse/"
                                            + cap.get_id().toString() + "/true\">Accept</a> " + "<a href=\""
                                            + rootUrl + "social/community/requestresponse/"
                                            + cap.get_id().toString() + "/false\">Deny</a>";

                                    SendMail mail = null;
                                    if (null != pp) {
                                        mail = new SendMail(new PropertiesManager().getAdminEmailAddress(),
                                                pp.getEmail(), subject, body);
                                    } else {
                                        mail = new SendMail(new PropertiesManager().getAdminEmailAddress(),
                                                getToAddressesFromCommunity(userGroup), subject, body);
                                    }

                                    if (mail.send("text/html")) {
                                        if (isSysAdmin) {
                                            if (null == pp)
                                                rp.setResponse(new ResponseObject("Invite Community", true,
                                                        "Invited user to community successfully: "
                                                                + cap.get_id().toString()));
                                            else
                                                rp.setResponse(new ResponseObject("Invite Community", true,
                                                        "Invited user group to community successfully: "
                                                                + cap.get_id().toString()));
                                        } else {
                                            if (null == pp)
                                                rp.setResponse(new ResponseObject("Invite Community", true,
                                                        "Invited user to community successfully"));
                                            else
                                                rp.setResponse(new ResponseObject("Invite Community", true,
                                                        "Invited user group to community successfully"));
                                        }
                                    } else {
                                        rp.setResponse(new ResponseObject("Invite Community", false,
                                                "The system was unable to email the invite for an unknown reason (eg an invite was required and the mail server is not setup)."));
                                    }
                                }
                            } else {
                                //otherwise just return we failed
                                rp.setResponse(new ResponseObject("Invite Community", false,
                                        "The user is already a member of this community."));
                            }
                        } else {
                            rp.setResponse(new ResponseObject("Invite Community", false,
                                    "Person/User Group does not exist"));
                        }
                    } else {
                        rp.setResponse(new ResponseObject("Invite Community", false,
                                "You must be owner to invite other members, if you received an invite, you must accept it through that"));
                    }
                } else {
                    rp.setResponse(new ResponseObject("Invite Community", false,
                            "Cannot invite members to personal community"));
                }
            } else {
                rp.setResponse(new ResponseObject("Invite Community", false, "Community does not exist"));
            }
        } catch (Exception ex) {
            rp.setResponse(new ResponseObject("Invite Community", false, Globals
                    .populateStackTrace(new StringBuffer("General Error, bad params maybe? "), ex).toString()));
        }
        return rp;
    }

    /**
     * Returns true if users can self register to this community
     * 
     * @param cp
     * @return
     */
    private boolean canSelfRegister(CommunityPojo cp) {
        if (cp != null) {
            if (cp.getCommunityAttributes().containsKey("usersCanSelfRegister")
                    && cp.getCommunityAttributes().get("usersCanSelfRegister").getValue().equals("true")) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns true if the given member is pending in the given community
     * 
     * @return
     */
    private boolean isMemberPending(CommunityPojo cp, ObjectId personOrUserGroupId) {
        for (CommunityMemberPojo cmp : cp.getMembers()) {
            if (cmp.get_id().equals(personOrUserGroupId)) {
                if (cmp.getUserStatus().equals("pending")) {
                    //found the user, and his status is pending
                    return true;
                }
                return false;
            }
        }
        return false;
        //TESTED only finds members that are pending while sysadmin
    }

    /**
     * requestResponse (REST)
     * @param requestIdStr
     * @param resp
     * @return
     */
    public ResponsePojo requestResponse(String requestIdStr, String resp,
            CommunityPojo.CommunityType communityType) {
        ResponsePojo rp = new ResponsePojo();
        try {
            // Check for valid text response value
            if (resp.equals("true") || resp.equals("false")) {
                // Attempt to retrieve the invite from the social.communityapprove collection
                DBObject dbo = DbManager.getSocial().getCommunityApprove()
                        .findOne(new BasicDBObject("_id", new ObjectId(requestIdStr)));

                if (dbo != null) {
                    CommunityApprovePojo cap = CommunityApprovePojo.fromDb(dbo, CommunityApprovePojo.class);
                    if (cap.getType().equals("source")) {
                        //approving a source
                        if (resp.equals("true")) {
                            rp = sourceHandler.approveSource(cap.getSourceId(), cap.getCommunityId(),
                                    cap.getRequesterId());
                        } else {
                            rp = sourceHandler.denySource(cap.getSourceId(), cap.getCommunityId(),
                                    cap.getRequesterId());
                        }
                        if (rp.getResponse().isSuccess()) {
                            //remove request object now
                            DbManager.getSocial().getCommunityApprove()
                                    .remove(new BasicDBObject("_id", new ObjectId(requestIdStr)));
                        }
                    } else {
                        //approving a user joining a community
                        BasicDBObject query = new BasicDBObject("_id", new ObjectId(cap.getCommunityId()));
                        DBObject dboComm = DbManager.getSocial().getCommunity().findOne(query);

                        //get user
                        BasicDBObject queryPerson = new BasicDBObject("_id", new ObjectId(cap.getPersonId()));
                        DBObject dboperson = DbManager.getSocial().getPerson().findOne(queryPerson);

                        // handle user vs user group:
                        ObjectId personOrUserGroupId = null;
                        PersonPojo pp = null;
                        CommunityPojo userGroup = null;
                        if (null == dboperson) {
                            userGroup = CommunityPojo.fromDb(
                                    DbManager.getSocial().getCommunity()
                                            .findOne(new BasicDBObject("_id", new ObjectId(cap.getPersonId()))),
                                    CommunityPojo.class);
                            if (null != userGroup)
                                personOrUserGroupId = userGroup.getId();

                            //(must be a user group etc etc if we got this far)
                        } else {
                            pp = PersonPojo.fromDb(dboperson, PersonPojo.class);
                            if (null != pp)
                                personOrUserGroupId = pp.get_id();
                        }
                        if ((null == pp) && (null == userGroup)) {
                            rp.setResponse(new ResponseObject("Request Response", false,
                                    "The user or user group does not exist."));
                            return rp;
                        } else if (dboComm != null) {
                            CommunityPojo cp = CommunityPojo.fromDb(dboComm, CommunityPojo.class);
                            boolean isStillPending = isMemberPending(cp, personOrUserGroupId);
                            //make sure the user is still waiting to join the community, otherwise remove this request and return
                            if (isStillPending) {
                                if (resp.equals("false")) {
                                    //if response is false (deny), always just remove user from community                     
                                    cp.removeMember(new ObjectId(cap.getPersonId()));
                                    /////////////////////////////////////////////////////////////////////////////////////////////////
                                    // TODO (INF-1214): Make this code more robust to handle changes to the community that need to
                                    // Caleb: this means change update to $set
                                    /////////////////////////////////////////////////////////////////////////////////////////////////
                                    DbManager.getSocial().getCommunity().update(query, cp.toDb());
                                } else {
                                    //if response is true (allow), always just add community info to user, and change status to active

                                    cp.updateMemberStatus(cap.getPersonId(), "active");
                                    /////////////////////////////////////////////////////////////////////////////////////////////////
                                    // TODO (INF-1214): Make this code more robust to handle changes to the community that need to
                                    // Caleb: this means change update to $set
                                    /////////////////////////////////////////////////////////////////////////////////////////////////
                                    DbManager.getSocial().getCommunity().update(query, cp.toDb());

                                    if (null != pp) {
                                        pp.addCommunity(cp);
                                        /////////////////////////////////////////////////////////////////////////////////////////////////
                                        // TODO (INF-1214): Make this code more robust to handle changes to the community that need to
                                        // Caleb: this means change update to $set
                                        /////////////////////////////////////////////////////////////////////////////////////////////////
                                        DbManager.getSocial().getPerson().update(queryPerson, pp.toDb());
                                    } else {
                                        //TODO (INF-2866): handle user group level updating
                                    }
                                }
                                //return successfully
                                rp.setResponse(new ResponseObject("Request Response", true,
                                        "Request answered successfully!"));
                            } else {
                                //return fail
                                rp.setResponse(new ResponseObject("Request Response", false,
                                        "Request has already been answered!"));
                            }
                            //remove request object now
                            DbManager.getSocial().getCommunityApprove()
                                    .remove(new BasicDBObject("_id", new ObjectId(requestIdStr)));

                        } else {
                            rp.setResponse(
                                    new ResponseObject("Request Response", false, "The community does not exist."));
                        }
                    }
                } else {
                    rp.setResponse(new ResponseObject("Request Response", false,
                            "This request does not exist, possibly answered already?"));
                }
            } else {
                rp.setResponse(new ResponseObject("Request Response", false, "Reponse must be true or false"));
            }
        } catch (Exception ex) {
            rp.setResponse(new ResponseObject("Request Response", false, Globals
                    .populateStackTrace(new StringBuffer("General Error, bad params maybe? "), ex).toString()));
        }
        return rp;
    }

    /**
     * updateCommunity (REST)
     * Updates a community found with communityId with fields from updateItem (communitypojo in json form)
     * Must be owner of communityId to update
     * @param userIdStr
     * @param communityIdStr
     * @param json
     * @return
     */
    public ResponsePojo updateCommunity(String userIdStr, String communityIdStr, String json,
            CommunityPojo.CommunityType communityType) {
        ResponsePojo rp = new ResponsePojo();
        try {
            communityIdStr = allowCommunityRegex(userIdStr, communityIdStr);
        } catch (Exception e) {
            rp.setResponse(new ResponseObject("Update Community", false,
                    "Error returning community info: " + e.getMessage()));
            return rp;
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////
        // Note: Only Sys Admins, Community Owner, and Community Moderators can add update communities
        boolean isOwnerOrModerator = SocialUtils.isOwnerOrModerator(communityIdStr, userIdStr);
        boolean isSysAdmin = RESTTools.adminLookup(userIdStr);
        boolean canUpdate = (isOwnerOrModerator || isSysAdmin) ? true : false;
        if (!canUpdate) {
            rp.setResponse(new ResponseObject("Update Community", false,
                    "User does not have permission to update the community."));
            return rp;
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////
        // Attempt to parse the JSON passed in to a CommunityPojo
        CommunityPojo updateCommunity = null;
        try {
            updateCommunity = ApiManager.mapFromApi(json, CommunityPojo.class, new CommunityPojoApiMap());
        } catch (Exception ex) {
            rp.setResponse(new ResponseObject("Update Community", false,
                    "Update json is badly formatted, could not deserialize."));
            return rp;
        }

        try {
            // Retrieve community we are trying to update from the database
            BasicDBObject query = new BasicDBObject("_id", new ObjectId(communityIdStr));
            DBObject dbo = DbManager.getSocial().getCommunity().findOne(query);
            String originalName = null;

            if (dbo != null) {

                CommunityPojo cp = CommunityPojo.fromDb(dbo, CommunityPojo.class);

                if (null == cp) {
                    rp.setResponse(
                            new ResponseObject("Update Community", false, "Community to update does not exist"));
                    return rp;
                }
                // (not allowed to change community type, too complex)
                if (cp.getType() != updateCommunity.getType()) {
                    rp.setResponse(new ResponseObject("Update Community", false,
                            "Can't update community type, currently: " + cp.getType().toString()));
                    return rp;
                }

                // Here are the fields you are allowed to change:
                // name:
                if (null != updateCommunity.getName()) {
                    // If you're changing name then ensure it's unique for consistency
                    //TODO (INF-1214): see addCommunity, this is currently something of a security hole
                    BasicDBObject nameCheck = new BasicDBObject("name", updateCommunity.getName());
                    nameCheck.put("_id", new BasicDBObject(MongoDbManager.ne_, cp.getId()));
                    if (null != MongoDbManager.getSocial().getCommunity().findOne(nameCheck)) {
                        rp.setResponse(new ResponseObject("Update Community", false,
                                "Can't change name to an existing community"));
                        return rp;
                    } //TESTED (tested changing names of existing community works...)      
                    originalName = cp.getName();
                    cp.setName(updateCommunity.getName());
                }
                if (null != updateCommunity.getDescription()) {
                    cp.setDescription(updateCommunity.getDescription());
                }
                if (null != updateCommunity.getTags()) {
                    cp.setTags(updateCommunity.getTags());
                }
                if ((null != updateCommunity.getCommunityAttributes()
                        && !updateCommunity.getCommunityAttributes().isEmpty())) {
                    cp.setCommunityAttributes(updateCommunity.getCommunityAttributes());
                }
                if ((null != updateCommunity.getCommunityUserAttribute()
                        && !updateCommunity.getCommunityUserAttribute().isEmpty())) {
                    cp.setCommunityUserAttribute(updateCommunity.getCommunityUserAttribute());
                }
                // Change owner: not allowed here, use community/update/status
                if ((null != updateCommunity.getOwnerId())
                        && !updateCommunity.getOwnerId().equals(cp.getOwnerId())) {
                    rp.setResponse(new ResponseObject("Update Community", false,
                            "Use community/update/status to change ownership"));
                    return rp;
                } //TESTED

                DbManager.getSocial().getCommunity().update(query, cp.toDb());

                // Community name has changed, member records need to be updated to reflect the name change
                if (originalName != null) {
                    DBObject query_person = new BasicDBObject("communities.name", originalName);
                    DBObject update_person = new BasicDBObject("$set",
                            new BasicDBObject("communities.$.name", updateCommunity.getName()));
                    DbManager.getSocial().getPerson().update(query_person, update_person, false, true);

                    //Also need to update share community names to reflect the name change
                    DBObject query_share = new BasicDBObject("communities.name", originalName);
                    DBObject update_share = new BasicDBObject("$set",
                            new BasicDBObject("communities.$.name", updateCommunity.getName()));
                    DbManager.getSocial().getShare().update(query_share, update_share, false, true);
                }

                /////////////////////////////////////////////////////////////////////////////////////////////////
                // TODO (INF-1214): Make this code more robust to handle changes to the community that need to 
                // propagate out to other records like Person
                // caleb note: 1/7 (change this to use $set is what this means, 
                // including above DbManager.getSocial().getCommunity().update(query, cp.toDb()); )
                // and the below unwritten communityuserattri
                /////////////////////////////////////////////////////////////////////////////////////////////////
                /////////////////////////////////////////////////////////////////////////////////////////////////
                // Community.communityUserAttribute
                // If user attributes have changed we might need to update member records...

                rp.setResponse(new ResponseObject("Update Community", true, "Community updated successfully."));
            } else {
                rp.setResponse(new ResponseObject("Update Community", false, "Community does not exist"));
            }
        } catch (Exception ex) {
            rp.setResponse(new ResponseObject("Update Community", false,
                    "Unable to update community. Error:" + ex.getMessage()));
        }
        return rp;
    }

    //////////////////////////////////////////////////////////////////////////
    //////////////////////// Helper Functions ////////////////////////////////
    //////////////////////////////////////////////////////////////////////////

    /**
     * addCommunityMember (only called internally and by PersonHandler)
     * @param communityIdStr
     * @param personIdStr
     * @param email
     * @param displayName
     * @param userType
     * @param userStatus
     * @return
     */
    private ResponsePojo addCommunityMember(String userIdStr, String communityIdStr, String communityName,
            String personIdStr, String email, String displayName, String userType, String userStatus) {
        return addCommunityMember(userIdStr, communityIdStr, communityName, personIdStr, email, displayName,
                userType, userStatus, false);
    }

    public ResponsePojo addCommunityMember(String userIdStr, String communityIdStr, String communityName,
            String personIdStr, String email, String displayName, String userType, String userStatus,
            boolean override) {
        /////////////////////////////////////////////////////////////////////////////////////////////////
        // Note: Only Sys Admins, Community Owner, and Community Moderators can add users to communities
        boolean isOwnerOrModerator = SocialUtils.isOwnerOrModerator(communityIdStr, userIdStr);
        boolean isSysAdmin = RESTTools.adminLookup(userIdStr);
        boolean canAdd = (isOwnerOrModerator || isSysAdmin || override) ? true : false;

        ResponsePojo rp = new ResponsePojo();

        if (canAdd) {
            try {
                // Find person record to update
                BasicDBObject query = new BasicDBObject();
                query.put("_id", new ObjectId(communityIdStr));

                DBObject dbo = DbManager.getSocial().getCommunity().findOne(query);

                if (dbo != null) {
                    // Get GsonBuilder object with MongoDb de/serializers registered
                    CommunityPojo community = CommunityPojo.fromDb(dbo, CommunityPojo.class);

                    // Get the list of existing members, check to see if user is already 
                    // a member of the community, make sure CommunityMember obj isn't null/empty
                    Boolean alreadyMember = false;
                    Set<CommunityMemberPojo> cmps = null;
                    if (community.getMembers() != null) {
                        cmps = community.getMembers();
                        for (CommunityMemberPojo c : cmps) {
                            if (c.get_id().toStringMongod().equals(personIdStr))
                                alreadyMember = true;
                        }
                    } else {
                        cmps = new HashSet<CommunityMemberPojo>();
                        community.setMembers(cmps);
                    }

                    if (!alreadyMember) {
                        // Note: This changes community owner
                        if (userType.equals("owner")) {
                            community.setOwnerId(new ObjectId(personIdStr));
                            community.setOwnerDisplayName(displayName);
                        }

                        // Create the new member object
                        CommunityMemberPojo cmp = new CommunityMemberPojo();
                        cmp.set_id(new ObjectId(personIdStr));
                        // (These all come from API - so applies to both users and user groups)
                        cmp.setEmail(email);
                        cmp.setDisplayName(displayName);
                        cmp.setUserStatus(userStatus);
                        cmp.setUserType(userType);

                        // Set the userAttributes based on default
                        Set<CommunityMemberUserAttributePojo> cmua = new HashSet<CommunityMemberUserAttributePojo>();
                        Map<String, CommunityUserAttributePojo> cua = community.getCommunityUserAttribute();

                        Iterator<Map.Entry<String, CommunityUserAttributePojo>> it = cua.entrySet().iterator();
                        while (it.hasNext()) {
                            CommunityMemberUserAttributePojo c = new CommunityMemberUserAttributePojo();
                            Map.Entry<String, CommunityUserAttributePojo> pair = it.next();
                            c.setType(pair.getKey().toString());
                            CommunityUserAttributePojo v = (CommunityUserAttributePojo) pair.getValue();
                            c.setValue(v.getDefaultValue());
                            cmua.add(c);
                        }
                        cmp.setUserAttributes(cmua);

                        // Get Person data to added to member record
                        BasicDBObject query2 = new BasicDBObject();
                        query2.put("_id", new ObjectId(personIdStr));
                        DBObject dbo2 = DbManager.getSocial().getPerson().findOne(query2);
                        PersonPojo p = PersonPojo.fromDb(dbo2, PersonPojo.class);

                        if (null == p) {
                            // This could be a user group instead ... note can't add user groups to user groups... 
                            CommunityPojo userGroup = CommunityPojo.fromDb(DbManager.getSocial().getCommunity()
                                    .findOne(new BasicDBObject("_id", cmp.get_id())), CommunityPojo.class);
                            if ((null != userGroup) && (CommunityType.user != userGroup.getType())) {
                                rp.setResponse(new ResponseObject("Add member to community", false,
                                        "Can't add data groups as members."));
                                return rp;
                            }
                            if (null == userGroup) {
                                rp.setResponse(new ResponseObject("Add member to community", false,
                                        "User/User Group not found."));
                                return rp;
                            } else if (CommunityPojo.CommunityType.user == community.getType()) {
                                rp.setResponse(new ResponseObject("Add member to community", false,
                                        "Can't add user group to user group."));
                                return rp;
                            } else {
                                //TODO (INF-2866): If doing it that way, then add community to all members 
                            }
                            cmp.setType(MemberType.user_group);

                        } //TESTED
                        else {
                            if (p.getContacts() != null) {
                                // Set contacts from person record
                                Set<CommunityMemberContactPojo> contacts = new HashSet<CommunityMemberContactPojo>();
                                Map<String, PersonContactPojo> pcp = p.getContacts();
                                Iterator<Map.Entry<String, PersonContactPojo>> it2 = pcp.entrySet().iterator();
                                while (it2.hasNext()) {
                                    CommunityMemberContactPojo c = new CommunityMemberContactPojo();
                                    Map.Entry<String, PersonContactPojo> pair = it2.next();
                                    c.setType(pair.getKey().toString());
                                    PersonContactPojo v = (PersonContactPojo) pair.getValue();
                                    c.setValue(v.getValue());
                                    contacts.add(c);
                                }
                                cmp.setContacts(contacts);
                            }

                            // Set links from person record
                            if (p.getLinks() != null) {
                                // Set contacts from person record
                                Set<CommunityMemberLinkPojo> links = new HashSet<CommunityMemberLinkPojo>();
                                Map<String, PersonLinkPojo> plp = p.getLinks();
                                Iterator<Map.Entry<String, PersonLinkPojo>> it3 = plp.entrySet().iterator();
                                while (it.hasNext()) {
                                    CommunityMemberLinkPojo c = new CommunityMemberLinkPojo();
                                    Map.Entry<String, PersonLinkPojo> pair = it3.next();
                                    c.setTitle(pair.getKey().toString());
                                    PersonLinkPojo v = (PersonLinkPojo) pair.getValue();
                                    c.setUrl(v.getUrl());
                                    links.add(c);
                                }
                                cmp.setLinks(links);
                            }
                            cmp.setType(MemberType.user);

                        } //(end if adding user _not_ user group to community) 

                        // Add new member object to the set
                        cmps.add(cmp);

                        /////////////////////////////////////////////////////////////////////////////////////////////////
                        // TODO (INF-1214): Make this code more robust to handle changes to the community that need to
                        // Caleb: this means change update to $set
                        /////////////////////////////////////////////////////////////////////////////////////////////////
                        DbManager.getSocial().getCommunity().update(query, community.toDb());

                        rp.setData(community, new CommunityPojoApiMap());

                        if (MemberType.user == cmp.getType()) {
                            PersonHandler person = new PersonHandler();
                            person.addCommunity(personIdStr, communityIdStr, communityName, community.getType());

                            rp.setResponse(new ResponseObject("Add member to community", true,
                                    "Person has been added as member of community"));
                        } else {
                            rp.setResponse(new ResponseObject("Add member to community", true,
                                    "User Group has been added to community"));
                        }
                    } else {
                        rp.setResponse(new ResponseObject("Add member to community", true,
                                "Person/Group is already a member of the community."));
                    }
                } else {
                    rp.setResponse(new ResponseObject("Add member to community", false, "Community not found."));
                }
            } catch (Exception e) {
                // If an exception occurs log the error
                logger.error("Exception Message: " + e.getMessage(), e);
                rp.setResponse(new ResponseObject("Add member to community", false,
                        "Error adding member to community " + e.getMessage()));
            }
        } else {
            rp.setResponse(new ResponseObject("Add member to community", false,
                    "The user does not have permissions to add a user to the community."));
        }

        return rp;
    }

    /**
     * removeCommunityMember (only called internally and from PersonHandler)
     * @param userIdStr
     * @param communityIdStr
     * @param personIdStr
     * @return ResponsePojo
     */
    public ResponsePojo removeCommunityMember(String userIdStr, String communityIdStr, String personIdStr) {
        /////////////////////////////////////////////////////////////////////////////////////////////////
        // Note: Only Sys Admins, Community Owner, and Community Moderators can remove users
        boolean isOwnerOrModerator = SocialUtils.isOwnerOrModerator(communityIdStr, userIdStr);
        boolean isSysAdmin = RESTTools.adminLookup(userIdStr);
        boolean canRemove = (isOwnerOrModerator || isSysAdmin || isRemovingSelf("remove", userIdStr, personIdStr))
                ? true
                : false;

        ResponsePojo rp = new ResponsePojo();

        if (canRemove) {
            try {
                // Find person record to update
                BasicDBObject query = new BasicDBObject();
                query.put("_id", new ObjectId(communityIdStr));
                DBObject dbo = DbManager.getSocial().getCommunity().findOne(query);

                if (dbo != null) {
                    // Get GsonBuilder object with MongoDb de/serializers registered
                    CommunityPojo community = CommunityPojo.fromDb(dbo, CommunityPojo.class);

                    Boolean isMember = false;

                    Set<CommunityMemberPojo> cmps = null;
                    CommunityMemberPojo cmp = null;
                    if (community.getMembers() != null) {
                        cmps = community.getMembers();
                        for (CommunityMemberPojo c : cmps) {
                            if (c.get_id().toStringMongod().equals(personIdStr)) {
                                cmp = c;
                                isMember = true;
                            }
                        }
                    }

                    if (isMember) {
                        cmps.remove(cmp);

                        /////////////////////////////////////////////////////////////////////////////////////////////////
                        // TODO (INF-1214): Make this code more robust to handle changes to the community that need to
                        // Caleb: this means change update to $set
                        /////////////////////////////////////////////////////////////////////////////////////////////////               
                        DbManager.getSocial().getCommunity().update(query, community.toDb());

                        if (CommunityMemberPojo.MemberType.user == cmp.getType()) {
                            PersonHandler person = new PersonHandler();
                            person.removeCommunity(personIdStr, communityIdStr);

                            rp.setData(community, new CommunityPojoApiMap());
                            rp.setResponse(new ResponseObject("Remove member from community", true,
                                    "Member has been removed from community"));
                        } else { // user group
                            //TODO (INF-2866): remove from community from each user, if that's the way we end up implementing this (?)                     
                            rp.setData(community, new CommunityPojoApiMap());
                            rp.setResponse(new ResponseObject("Remove member from community", true,
                                    "User Group has been removed from community"));
                        }
                    } else {
                        rp.setResponse(new ResponseObject("Remove member from community", true,
                                "Person/Group is not a member of this community."));
                    }
                } else {
                    rp.setResponse(
                            new ResponseObject("Remove member from community", false, "Community not found."));
                }
            } catch (Exception e) {
                // If an exception occurs log the error
                logger.error("Exception Message: " + e.getMessage(), e);
                rp.setResponse(new ResponseObject("Remove member from community", false,
                        "Error removing member from community " + e.getMessage()));
            }
        } else {
            rp.setResponse(new ResponseObject("Remove member from community", false,
                    "The user does not have permissions to remove a user from the community."));
        }

        return rp;
    }

    /**
     * createSelfCommunity
     * Creates a personal group for the given user and adds them to it.
     * @param person
     */
    public void createSelfCommunity(PersonPojo person) {
        try {
            //create community
            CommunityPojo selfCommunity = new CommunityPojo();
            selfCommunity.setId(person.get_id()); //set to same as users
            selfCommunity.setName(person.getDisplayName() + "'s Personal Community");
            selfCommunity.setDescription(person.getDisplayName() + "'s Personal Community");
            selfCommunity.setCreated(new Date());
            selfCommunity.setModified(new Date());
            selfCommunity.setIsPersonalCommunity(true);
            Map<String, CommunityAttributePojo> commAttributes = new HashMap<String, CommunityAttributePojo>();
            commAttributes.put("isPublic", new CommunityAttributePojo("boolean", "false"));
            commAttributes.put("usersCanSelfRegister", new CommunityAttributePojo("boolean", "false"));
            commAttributes.put("registrationRequiresApproval", new CommunityAttributePojo("boolean", "false"));
            commAttributes.put("usersCanCreateSubCommunities", new CommunityAttributePojo("boolean", "false"));
            selfCommunity.setCommunityAttributes(commAttributes);
            Map<String, CommunityUserAttributePojo> commUserAttributes = new HashMap<String, CommunityUserAttributePojo>();
            commUserAttributes.put("publishLoginToActivityFeed",
                    new CommunityUserAttributePojo("boolean", "true", true));
            commUserAttributes.put("publishCommentsToActivityFeed",
                    new CommunityUserAttributePojo("boolean", "true", true));
            commUserAttributes.put("publishSharingToActivityFeed",
                    new CommunityUserAttributePojo("boolean", "true", true));
            commUserAttributes.put("publishQueriesToActivityFeed",
                    new CommunityUserAttributePojo("boolean", "true", true));
            commUserAttributes.put("publishCommentsPublicly",
                    new CommunityUserAttributePojo("boolean", "false", true));
            selfCommunity.setCommunityUserAttribute(commUserAttributes);

            // Create the index form of the community:
            try {
                GenericProcessingController.createCommunityDocIndex(selfCommunity.getId().toString(), null, true,
                        false, false);
                //TESTED
            } catch (Exception e) {
            } // Do nothing, will have to update the user to stop bad things from happening on query though

            //write community to db
            DbManager.getSocial().getCommunity().insert(selfCommunity.toDb());
            //update user to be in this community
            PersonCommunityPojo pcpSelf = new PersonCommunityPojo(person.get_id(), selfCommunity.getName());
            person.getCommunities().add(pcpSelf);
            /////////////////////////////////////////////////////////////////////////////////////////////////
            // TODO (INF-1214): Make this code more robust to handle changes to the community that need to
            // Caleb: this means change update to $set
            /////////////////////////////////////////////////////////////////////////////////////////////////
            DbManager.getSocial().getPerson().update(new BasicDBObject("_id", person.get_id()), person.toDb());
        } catch (Exception ex) {

        }
    }

    //////////////////////////////////////////////////////////////////////////
    //////////////////////// Helper Functions ////////////////////////////////
    ///////// These functions do not get called from the public API //////////
    //////////////////////////////////////////////////////////////////////////

    /**
     * getToAddressesFromCommunity (Internal)
     * @param cp
     * @return
     */
    private static String getToAddressesFromCommunity(CommunityPojo cp) {
        StringBuffer emailAddresses = new StringBuffer();
        emailAddresses.append(cp.getOwner().getEmail());

        for (CommunityMemberPojo cm : cp.getMembers()) {
            if (cm.getUserType().equalsIgnoreCase("moderator")) {
                if (CommunityMemberPojo.MemberType.user == cm.getType()) {
                    emailAddresses.append(";" + cm.getEmail());
                } else {// user group

                    CommunityPojo userGroup = CommunityPojo.fromDb(
                            DbManager.getSocial().getCommunity().findOne(new BasicDBObject("_id", cm.get_id())),
                            CommunityPojo.class);

                    if (null != userGroup) {
                        emailAddresses.append(";" + userGroup.getOwner().getEmail());
                        for (CommunityMemberPojo cm2 : userGroup.getMembers()) {
                            if (CommunityMemberPojo.MemberType.user_group != cm.getType()) {
                                emailAddresses.append(";" + cm2.getEmail());
                            }
                            //(don't support recursion, so just ignore from here)
                        }
                    }
                } //TESTED
            }
        }
        return emailAddresses.toString();
    }

    /**
     * getTagsFromString
     * @param t
     * @return List<String>
     */
    private List<String> getTagsFromString(String t) {
        List<String> tags = new ArrayList<String>();
        if (t != null) {
            try {
                String[] a = t.split(",");
                for (String v : a) {
                    tags.add(v);
                }
            } catch (Exception e) {
            }
        }
        return tags;
    }

    /**
     * getDefaultCommunityAttributes
     * @return Map<String, CommunityAttributePojo
     */
    private Map<String, CommunityAttributePojo> getDefaultCommunityAttributes() {
        Map<String, CommunityAttributePojo> c = new HashMap<String, CommunityAttributePojo>();
        CommunityAttributePojo v = new CommunityAttributePojo();
        v.setType("Boolean");
        v.setValue("false");
        c.put("isPublic", v);
        v = new CommunityAttributePojo();
        v.setType("Boolean");
        v.setValue("true");
        c.put("usersCanSelfRegister", v);
        v = new CommunityAttributePojo();
        v.setType("Boolean");
        v.setValue("true");
        c.put("registrationRequiresApproval", v);
        v = new CommunityAttributePojo();
        v.setType("Boolean");
        v.setValue("false");
        c.put("usersCanCreateSubCommunities", v);
        return c;
    }

    /**
     * getDefaultCommunityUserAttributes
     * @return Map<String, CommunityUserAttributePojo>
     */
    private Map<String, CommunityUserAttributePojo> getDefaultCommunityUserAttributes() {
        Map<String, CommunityUserAttributePojo> c = new HashMap<String, CommunityUserAttributePojo>();
        CommunityUserAttributePojo v = new CommunityUserAttributePojo();
        v.setType("Boolean");
        v.setDefaultValue("true");
        v.setAllowOverride(false);
        c.put("publishLoginToActivityFeed", v);
        v = new CommunityUserAttributePojo();
        v.setType("Boolean");
        v.setDefaultValue("true");
        v.setAllowOverride(false);
        c.put("publishCommentsToActivityFeed", v);
        v = new CommunityUserAttributePojo();
        v.setType("Boolean");
        v.setDefaultValue("true");
        v.setAllowOverride(false);
        c.put("publishSharingToActivityFeed", v);
        v = new CommunityUserAttributePojo();
        v.setType("Boolean");
        v.setDefaultValue("true");
        v.setAllowOverride(false);
        c.put("publishQueriesToActivityFeed", v);
        v = new CommunityUserAttributePojo();
        v.setType("Boolean");
        v.setDefaultValue("false");
        v.setAllowOverride(false);
        c.put("publishCommentsPublicly", v);
        return c;
    }

    /**
     * Utility function to remove members if users are not suppose to see them
     * 
     * @param inputCommunities
     * @param isAdminModerator
     * @return
     */
    private CommunityPojo filterCommunityMembers(CommunityPojo community, boolean isAdmin, String userId) {
        //an admin can see everything
        if (!(isAdmin || SocialUtils.isOwnerOrModerator(community.getId().toString(), userId))) {
            //if community has publish members turned off, remove the members
            if (community.getCommunityAttributes().containsKey("publishMemberOverride")
                    && community.getCommunityAttributes().get("publishMemberOverride").getValue().equals("false")) {
                community.setMembers(null);
            }
        }
        return community;
    }

    private List<CommunityPojo> filterCommunityMembers(List<CommunityPojo> inputCommunities, boolean isAdmin,
            String userId) {
        //an admin can see everything
        if (!isAdmin) {
            for (CommunityPojo community : inputCommunities) {
                filterCommunityMembers(community, isAdmin, userId);
            }
        }
        return inputCommunities;
    }

    // Utility: make life easier in terms of adding/update/inviting/leaving from the command line

    private static String allowCommunityRegex(String userIdStr, String communityIdStr) {
        if (communityIdStr.startsWith("*")) {
            String[] communityIdStrs = SocialUtils.getCommunityIds(userIdStr, communityIdStr);
            if (1 == communityIdStrs.length) {
                communityIdStr = communityIdStrs[0];
            } else if (communityIdStrs.length > 0) {
                throw new RuntimeException("Invalid community pattern (many): " + Arrays.toString(communityIdStrs));
            } else {
                throw new RuntimeException("Invalid community pattern (none)");
            }
        }
        return communityIdStr;
    }

    // UTILITY: data/user group handling

    private BasicDBObject addCommunityTypeTerm(BasicDBObject query, CommunityType type) {
        if (CommunityType.data == type) { // ie both combined and data (except personal/combined)
            query.put("type", new BasicDBObject(DbManager.ne_, CommunityType.user.toString()));
            query.put("isPersonalCommunity", false);
        } else if (CommunityType.user == type) { // (user only)
            query.put("type", CommunityType.user.toString());
        }
        return query;
    }//TESTED (by hand)

}