package crmdna.member; import com.googlecode.objectify.Key; import com.googlecode.objectify.ObjectifyFilter; import crmdna.client.Client; import crmdna.common.DateUtils; import crmdna.common.DateUtils.Month; import crmdna.common.MemcacheLock; import crmdna.common.Utils; import crmdna.common.api.APIException; import crmdna.common.api.APIResponse.Status; import crmdna.common.contact.Contact; import crmdna.common.contact.ContactProp; import crmdna.group.Group; import crmdna.list.ListEntity; import crmdna.list.ListProp; import crmdna.mail2.MailMap; import crmdna.member.MemberEntity.MemberFactory; import crmdna.program.Program; import crmdna.program.ProgramEntity; import crmdna.program.ProgramProp; import crmdna.programtype.ProgramType; import crmdna.user.User; import crmdna.user.User.GroupLevelPrivilege; import crmdna.user.User.ResourceType; import crmdna.user.UserCore; import java.util.*; import static crmdna.common.AssertUtils.*; import static crmdna.common.OfyService.ofy; import static crmdna.member.MemberBulkSaver.memberBulkSaver; public class Member { public static MemberProp create(String client, long groupId, ContactProp contact, boolean allowDuplicateEmail, String login) { Client.ensureValid(client); User.ensureValidUser(client, login); Group.safeGet(client, groupId); ensureNotNull(contact, "contact is null"); Contact.ensureEmailOrPhoneNumberValid(contact); if (null != contact.email) contact.email = contact.email.toLowerCase(); if (!allowDuplicateEmail) { // if email is specified - it should be unique if (null != contact.email) { ensureEmailNotPresentInDB(client, contact.email, login); } } try (MemcacheLock lock = new MemcacheLock(client, ResourceType.MEMBER, contact.email)) { MemberEntity memberEntity = MemberFactory.create(client, 1).get(0); memberBulkSaver(client, Utils.getList(memberEntity)) .setContactsSameSizeList(Utils.getList(contact)).addGroupToAll(groupId) .populateDependantsAndSave(); ObjectifyFilter.complete(); return memberEntity.toProp(); } } static void ensureEmailNotPresentInDB(String client, String email, String login) { ensureNotNull(email); email = email.toLowerCase(); MemberQueryCondition condition = new MemberQueryCondition(client, 10000); condition.email = email; List<Key<MemberEntity>> keys = MemberLoader.queryKeys(condition, login); if (keys.size() != 0) throw new APIException("There is already a member with email [" + email + "]") .status(Status.ERROR_RESOURCE_ALREADY_EXISTS); } public static MemberProp updateContactDetails(final String client, final long memberId, final ContactProp contact, String login) { Client.ensureValid(client); ensureNotNull(contact, "contact is null"); ensureNotNull(login, "login is null"); MemberEntity memberEntity = MemberLoader.safeGet(client, memberId, login); // member can update his own record, otherwise will need to be valid user if ((memberEntity.email == null) || !memberEntity.email.equalsIgnoreCase(login)) User.ensureValidUser(client, login); // if email is different it should not exist in a different entity if ((memberEntity.email != null) && (contact.email != null) && (!memberEntity.email.toLowerCase().equals(contact.email.toLowerCase()))) { contact.email = contact.email.toLowerCase(); ensureEmailNotPresentInDB(client, contact.email, login); } try (MemcacheLock lock = new MemcacheLock(client, ResourceType.MEMBER, contact.email)) { memberBulkSaver(client, Utils.getList(memberEntity)).setContactsSameSizeList( Utils.getList(contact)).populateDependantsAndSave(); } return memberEntity.toProp(); } public static MemberProp addOrDeleteGroup(String client, long memberId, long groupId, boolean add, String login) { Client.ensureValid(client); Group.safeGet(client, groupId); MemberEntity memberEntity = MemberLoader.safeGet(client, memberId, login); boolean change; if (add) change = memberEntity.groupIds.add(groupId); else { change = memberEntity.groupIds.remove(groupId); if (memberEntity.groupIds.isEmpty()) throw new APIException("The only group [" + groupId + "] cannot be removed from member [" + memberId + "]").status(Status.ERROR_PRECONDITION_FAILED); } if (change) ofy(client).save().entity(memberEntity).now(); return memberEntity.toProp(); } public static MemberProp addOrDeleteProgram(String client, long memberId, long programId, boolean add, String login) { Client.ensureValid(client); UserCore.ensureValidUser(client, login); MemberEntity memberEntity = MemberLoader.safeGet(client, memberId, login); Program.safeGet(client, programId); if (add) { memberBulkSaver(client, Utils.getList(memberEntity)).addProgramToAll(programId) .populateDependantsAndSave(); } else { memberBulkSaver(client, Utils.getList(memberEntity)).deleteProgramFromAll(programId) .populateDependantsAndSave(); } return memberEntity.toProp(); } //returns true if member entity changed public static boolean addOrDeleteList(String client, long memberId, long listId, boolean add, String login) { Client.ensureValid(client); ListProp listProp = crmdna.list.List.safeGet(client, listId).toProp(); MemberEntity memberEntity = MemberLoader.safeGet(client, memberId, login); if (!listProp.enabled) throw new APIException("List [" + listId + "] is disabled") .status(Status.ERROR_PRECONDITION_FAILED); //need privilege if not self or list is restricted if (!memberEntity.isSelf(login) || listProp.restricted) { User.ensureGroupLevelPrivilege(client, listProp.groupId, login, GroupLevelPrivilege.UPDATE_LIST); } boolean change; if (add) change = memberEntity.listIds.add(listId); else { change = memberEntity.listIds.remove(listId); } populateDependantFields(client, Utils.getList(memberEntity)); if (change) ofy(client).save().entity(memberEntity).now(); return change; } // returns true if member list changed as a result of this call, else false public static boolean subscribeList_to_be_removed(String client, long memberId, long listId, String login) { Client.ensureValid(client); MemberEntity memberEntity = MemberLoader.safeGet(client, memberId, login); ListProp listProp = crmdna.list.List.safeGet(client, listId).toProp(); if (!listProp.enabled) throw new APIException("Cannot (un)subscribe for disabled list [" + listId + "]") .status(Status.ERROR_PRECONDITION_FAILED); //need privilege if not self or list is restricted if (!memberEntity.isSelf(login) || listProp.restricted) { User.ensureGroupLevelPrivilege(client, listProp.groupId, login, GroupLevelPrivilege.UPDATE_LIST); } if (memberEntity.subscribedListIds.contains(listId)) return false; memberEntity.subscribedListIds.add(listId); populateDependantFields(client, Utils.getList(memberEntity)); ofy(client).save().entity(memberEntity).now(); return true; } // returns true if member list changed as a result of this call, else false public static boolean unsubscribeList_to_be_removed(String client, long memberId, long listId, String login) { Client.ensureValid(client); MemberEntity memberEntity = MemberLoader.safeGet(client, memberId, login); ListProp listProp = crmdna.list.List.safeGet(client, listId).toProp(); if (!memberEntity.isSelf(login)) User.ensureGroupLevelPrivilege(client, listProp.groupId, login, GroupLevelPrivilege.UPDATE_LIST); if (!memberEntity.unsubscribedListIds.contains(listId)) return false; memberEntity.unsubscribedListIds.remove(listId); populateDependantFields(client, Utils.getList(memberEntity)); ofy(client).save().entity(memberEntity).now(); return true; } // returns true if member list changed as a result of this call, else false public static boolean subscribeGroup(String client, long memberId, long groupId, String login) { Client.ensureValid(client); MemberEntity memberEntity = MemberLoader.safeGet(client, memberId, login); Group.safeGet(client, groupId); if (!memberEntity.isSelf(login)) { User.ensureValidUser(client, login); if (memberEntity.unsubscribedGroupIds.contains(groupId)){ User.ensureClientLevelPrivilege(client, login, User.ClientLevelPrivilege.SUBSCRIBE_GROUP); } } if (memberEntity.subscribedGroupIds.contains(groupId)) { return false; } memberEntity.subscribedGroupIds.add(groupId); memberEntity.unsubscribedGroupIds.remove(groupId); ofy(client).save().entity(memberEntity).now(); return true; } // returns true if member list changed as a result of this call, else false public static boolean unsubscribeGroup(String client, long memberId, long groupId, String login) { Client.ensureValid(client); MemberEntity memberEntity = MemberLoader.safeGet(client, memberId, login); Group.safeGet(client, groupId); if (!memberEntity.isSelf(login)) { User.ensureValidUser(client, login); } if (memberEntity.unsubscribedGroupIds.contains(groupId)) { return false; } memberEntity.subscribedGroupIds.remove(groupId); memberEntity.unsubscribedGroupIds.add(groupId); ofy(client).save().entity(memberEntity).now(); return true; } public static Long getMatchingMemberId(String client, String email, String firstName, String lastName) { List<ContactProp> contactDetailProps = new ArrayList<>(); ContactProp c = new ContactProp(); c.firstName = firstName; c.lastName = lastName; c.email = email; contactDetailProps.add(c); return getMatchingMemberIds(client, contactDetailProps).get(0); } public static Map<String, Long> getMemberIdFromEmailIfExistsElseCreateAndGet(String client, MailMap mailMap, long groupId) { Client.ensureValid(client); Group.safeGet(client, groupId); ensureNotNull(mailMap, "mailMap is null"); Set<String> emails = mailMap.getEmails(); if (emails.isEmpty()) return new HashMap<>(); List<Key<MemberEntity>> memberKeys = ofy(client).load().type(MemberEntity.class).filter("email in", emails).keys().list(); List<MemberEntity> memberEntities = new ArrayList<>(); if (!memberKeys.isEmpty()) memberEntities = ofy(client).load().type(MemberEntity.class).filterKey("in", memberKeys).project("email") .list(); Map<String, Long> emailVsMemberId = new HashMap<>(); Set<String> missing = new HashSet<>(emails); for (MemberEntity memberEntity : memberEntities) { if (memberEntity.email == null) continue; if (emails.contains(memberEntity.email)) { missing.remove(memberEntity.email); emailVsMemberId.put(memberEntity.email, memberEntity.memberId); } } if (missing.isEmpty()) return emailVsMemberId; // create member entities for missing emails ensure(!missing.isEmpty()); List<MemberEntity> newMemberEntities = MemberFactory.create(client, missing.size()); ensureEqual(missing.size(), newMemberEntities.size(), "newMemberEntities size mismatch"); List<ContactProp> contacts = new ArrayList<>(); for (String email : emails) { if (!missing.contains(email)) continue; ContactProp contact = new ContactProp(); contact.asOfyyyymmdd = DateUtils.toYYYYMMDD(new Date()); contact.firstName = mailMap.get(MailMap.MergeVarID.FIRST_NAME, email); contact.lastName = mailMap.get(MailMap.MergeVarID.LAST_NAME, email); contact.email = email; contacts.add(contact); } memberBulkSaver(client, newMemberEntities).setContactsSameSizeList(contacts) .addGroupToAll(groupId).populateDependantsAndSave(); for (MemberEntity memberEntity : newMemberEntities) { emailVsMemberId.put(memberEntity.email, memberEntity.memberId); } return emailVsMemberId; } public static Map<String, Long> getMemberIdFromEmail(String client, Set<String> emails) { Client.ensureValid(client); ensureNoNullElement(emails); if (emails.isEmpty()) return new HashMap<>(); Map<String, List<Key<MemberEntity>>> emailVsKeys = new HashMap<>(); for (String email : emails) { List<Key<MemberEntity>> keys = ofy(client).load().type(MemberEntity.class).filter("email", email).keys().list(); emailVsKeys.put(email, keys); } Map<String, Long> emailVsMemberId = new HashMap<>(); for (String email : emailVsKeys.keySet()) { List<Key<MemberEntity>> keys = emailVsKeys.get(email); if (!keys.isEmpty()) { long memberId = keys.get(0).getId(); emailVsMemberId.put(email, memberId); } } return emailVsMemberId; } public static List<Long> getMatchingMemberIds(String client, List<ContactProp> contactDetailProps) { Client.ensureValid(client); if (contactDetailProps.size() == 0) return new ArrayList<>(); for (ContactProp contactDetailProp : contactDetailProps) { if (contactDetailProp.email != null) contactDetailProp.email = contactDetailProp.email.toLowerCase(); } Map<String, Set<Integer>> emailMap = new HashMap<>(); Map<String, Set<Integer>> mobileMap = new HashMap<>(); for (int index = 0; index < contactDetailProps.size(); index++) { ContactProp c = contactDetailProps.get(index); if (c.email != null) { if (!emailMap.containsKey(c.email)) emailMap.put(c.email, new HashSet<Integer>()); Set<Integer> existing = emailMap.get(c.email); existing.add(index); } else if (c.mobilePhone != null) { if (!mobileMap.containsKey(c.mobilePhone)) mobileMap.put(c.mobilePhone, new HashSet<Integer>()); Set<Integer> existing = mobileMap.get(c.mobilePhone); existing.add(index); } } List<Long> memberIds = new ArrayList<>(); for (int i = 0; i < contactDetailProps.size(); i++) { memberIds.add(null); } List<MemberEntity> emailMatches = new ArrayList<>(); if (emailMap.size() != 0) emailMatches = ofy(client).load().type(MemberEntity.class).filter("email in", emailMap.keySet()).list(); List<MemberEntity> mobileMatches = new ArrayList<>(); if (mobileMap.size() != 0) { mobileMatches = ofy(client).load().type(MemberEntity.class).filter("mobilePhone in", mobileMap.keySet()) .list(); } for (MemberEntity me : emailMatches) { MemberProp mp = me.toProp(); String memberName = mp.contact.getName(); for (Integer index : emailMap.get(mp.contact.email)) { String particantName = contactDetailProps.get(index).getName(); if (Utils.closeEnough(memberName, particantName)) memberIds.set(index, mp.memberId); } } for (MemberEntity me : mobileMatches) { MemberProp mp = me.toProp(); String memberName = mp.contact.getName(); for (Integer index : mobileMap.get(mp.contact.mobilePhone)) { if (memberIds.get(index) == null) { String particantName = contactDetailProps.get(index).getName(); if (Utils.closeEnough(memberName, particantName)) memberIds.set(index, mp.memberId); } } } return memberIds; } public static List<MemberEntity> getMatchingMembersSameSizeList(String client, List<ContactProp> contacts, String login) { // Returns a list same size as contacts // list element contains null when there is no match, else matching // member id Client.ensureValid(client); ensureNotNull(contacts, "contacts is null"); Set<String> firstName3Chars = new HashSet<>(); for (ContactProp c : contacts) { if ((c == null) || (c.firstName == null) || (c.firstName.length() < 3)) continue; String firstName3Char = c.firstName.substring(0, 3).toLowerCase(); firstName3Chars.add(firstName3Char); } MemberQueryCondition qc = new MemberQueryCondition(client, 10000); qc.firstName3Chars = firstName3Chars; List<Key<MemberEntity>> matchingMemberKeys = MemberLoader.queryKeys(qc, login); Iterable<MemberEntity> entities = ofy(client).load().keys(matchingMemberKeys).values(); Map<String, Set<MemberEntity>> firstName3CharVsEntity = new HashMap<>(); // multimap for (MemberEntity entity : entities) { String firstName3Char = entity.firstName3Char; if (!firstName3CharVsEntity.containsKey(firstName3Char)) { firstName3CharVsEntity.put(firstName3Char, new HashSet<MemberEntity>()); } firstName3CharVsEntity.get(firstName3Char).add(entity); } List<MemberEntity> matchingEntities = new ArrayList<>(contacts.size()); for (ContactProp c : contacts) { if ((c == null) || (c.firstName == null) || (c.firstName.length() < 3)) { matchingEntities.add(null); continue; } String firstName3Char = c.firstName.substring(0, 3).toLowerCase(); if (!firstName3CharVsEntity.containsKey(firstName3Char)) { // no matching member matchingEntities.add(null); continue; } Set<MemberEntity> set = firstName3CharVsEntity.get(firstName3Char); MemberEntity entity = findMatchingMember(c, set); matchingEntities.add(entity); } ensureEqual(contacts.size(), matchingEntities.size()); return matchingEntities; } public static MemberEntity findMatchingMember(ContactProp contactProp, Set<MemberEntity> memberEntities) { ensureNotNull(contactProp, "contactProp cannot be null"); ensureNotNull(memberEntities, "memberEntities cannot be null"); for (MemberEntity memberEntity : memberEntities) { if (Contact.isMatching(contactProp, memberEntity.toProp().contact)) return memberEntity; } return null; } static boolean populateContactDetails(MemberEntity memberEntity, ContactProp contact) { // TODO: use a smarter home address comparison function DateUtils.ensureFormatYYYYMMDD(contact.asOfyyyymmdd); if (memberEntity.asOfYYYYMMDD > contact.asOfyyyymmdd) return false; boolean changed = false; if (contact.email != null) { Utils.ensureValidEmail(contact.email); if (Utils.isDifferentCaseInsensitive(memberEntity.email, contact.email)) { memberEntity.email = contact.email.toLowerCase(); changed = true; } } if (contact.firstName != null) { if (Utils.isDifferentCaseInsensitive(memberEntity.firstName, contact.firstName)) { memberEntity.firstName = contact.firstName; changed = true; } } if (contact.lastName != null) { if (Utils.isDifferentCaseInsensitive(memberEntity.lastName, contact.lastName)) { memberEntity.lastName = contact.lastName; changed = true; } } if (contact.gender != null) { if (memberEntity.gender != contact.gender) changed = true; memberEntity.gender = contact.gender; } if (contact.homeAddress.address != null) { if (Utils.isDifferentCaseInsensitive(memberEntity.homeAddress, contact.homeAddress.address)) { memberEntity.homeAddress = contact.homeAddress.address; changed = true; } } if (contact.homeAddress.city != null) { if (Utils.isDifferentCaseInsensitive(memberEntity.homeCity, contact.homeAddress.city)) { memberEntity.homeCity = contact.homeAddress.city; changed = true; } } if (contact.homeAddress.state != null) { if (Utils.isDifferentCaseInsensitive(memberEntity.homeState, contact.homeAddress.state)) { memberEntity.homeState = contact.homeAddress.state; changed = true; } } if (contact.homeAddress.pincode != null) { if (Utils.isDifferentCaseInsensitive(memberEntity.homePincode, contact.homeAddress.pincode)) { memberEntity.homePincode = contact.homeAddress.pincode; changed = true; } } if (contact.homeAddress.country != null) { if (Utils.isDifferentCaseInsensitive(memberEntity.homeCountry, contact.homeAddress.country)) { memberEntity.homeCountry = contact.homeAddress.country; changed = true; } } if (contact.officeAddress.address != null) { if (Utils.isDifferentCaseInsensitive(memberEntity.officeAddress, contact.officeAddress.address)) { memberEntity.officeAddress = contact.officeAddress.address; changed = true; } } if (contact.company != null) { if (Utils.isDifferentCaseInsensitive(memberEntity.company, contact.company)) { memberEntity.company = contact.company; changed = true; } } if (contact.occupation != null) { if (Utils.isDifferentCaseInsensitive(memberEntity.occupation, contact.occupation)) { memberEntity.occupation = contact.occupation; changed = true; } } if (contact.officeAddress.pincode != null) { if (Utils.isDifferentCaseInsensitive(memberEntity.officePincode, contact.officeAddress.pincode)) { memberEntity.officePincode = contact.officeAddress.pincode; changed = true; } } if (contact.homePhone != null) { Utils.ensureValidPhoneNumber(contact.homePhone); if (Utils.isDifferentCaseInsensitive(memberEntity.homePhone, contact.homePhone)) { memberEntity.homePhone = contact.homePhone; changed = true; } } if (contact.officePhone != null) { Utils.ensureValidPhoneNumber(contact.officePhone); if (Utils.isDifferentCaseInsensitive(memberEntity.officePhone, contact.officePhone)) { memberEntity.officePhone = contact.officePhone; changed = true; } } if (contact.mobilePhone != null) { Utils.ensureValidPhoneNumber(contact.mobilePhone); if (Utils.isDifferentCaseInsensitive(memberEntity.mobilePhone, contact.mobilePhone)) { memberEntity.mobilePhone = contact.mobilePhone; changed = true; } } if (changed) memberEntity.asOfYYYYMMDD = contact.asOfyyyymmdd; return changed; } static void populateDependantFields(String client, List<MemberEntity> memberEntities) { ensureNotNull(memberEntities, "memberEntities cannot be null"); for (MemberEntity memberEntity : memberEntities) { ensureNotNull(memberEntity, "individual memberEntity cannot be null"); populateDependantFieldsWithoutDBQuery(memberEntity); } // fields that involve database queries List<Long> programIds = new ArrayList<>(); for (MemberEntity memberEntity : memberEntities) { programIds.addAll(memberEntity.programIds); } Map<Long, ProgramProp> programProps = Program.getProps(client, programIds); for (MemberEntity memberEntity : memberEntities) { memberEntity.programTypeIds.clear(); memberEntity.practiceIds.clear(); for (long programId : memberEntity.programIds) { if (programProps.containsKey(programId)) { ProgramProp programProp = programProps.get(programId); memberEntity.programTypeIds.add(programProp.programTypeProp.programTypeId); } } for (Integer i : memberEntity.uvpMap.keySet()) { long programTypeId = memberEntity.uvpMap.get(i).programTypeId; memberEntity.programTypeIds.add(programTypeId); } memberEntity.practiceIds = ProgramType.getPracticeIds(client, memberEntity.programTypeIds); memberEntity.practiceIds.addAll(crmdna.list.List.getPracticeIds(client, memberEntity.listIds)); } } static MemberEntity populateDependantFieldsWithoutDBQuery(MemberEntity memberEntity) { // nameFirstChar if (memberEntity.firstName != null) { memberEntity.name = memberEntity.firstName.toLowerCase(); } else if (memberEntity.email != null) memberEntity.name = memberEntity.email.toLowerCase(); else if (memberEntity.mobilePhone != null) memberEntity.name = memberEntity.mobilePhone.toLowerCase(); else if (memberEntity.homePhone != null) memberEntity.name = memberEntity.homePhone.toLowerCase(); else if (memberEntity.officePhone != null) memberEntity.name = memberEntity.officePhone.toLowerCase(); else { // should never come here as either email or a valid phone number // should be specified throw new APIException() .status(Status.ERROR_RESOURCE_INCORRECT) .message( "Either email or a valid phone number should be specified when adding or updating member"); } memberEntity.nameFirstChar = memberEntity.name.substring(0, 1); if ((memberEntity.firstName != null) && (memberEntity.firstName.length() > 2)) memberEntity.firstName3Char = memberEntity.firstName.substring(0, 3).toLowerCase(); memberEntity.qsTags = Utils.getQSTags(memberEntity.email, memberEntity.firstName, memberEntity.lastName, memberEntity.homePhone, memberEntity.officePhone, memberEntity.mobilePhone); return memberEntity; } public static List<UnverifiedProgramProp> addUnverifiedProgram(String client, long memberId, long programTypeId, Month month, int year, String city, String country, String teacher, String login) { Client.ensureValid(client); MemberEntity entity = MemberLoader.safeGet(client, memberId, login); // this also checks if (login is same as member) or (login is a valid user) ProgramType.safeGet(client, programTypeId); ensure(year > 1970, "year should be after 1971"); DateUtils.ensureDateNotInFuture(month, year); int unverifiedProgramId; if (entity.uvpMap.isEmpty()) { unverifiedProgramId = 1; } else { unverifiedProgramId = entity.uvpMap.lastEntry().getValue().unverifiedProgramId + 1; } UnverifiedProgramProp prop = new UnverifiedProgramProp(); prop.unverifiedProgramId = unverifiedProgramId; prop.programTypeId = programTypeId; prop.month = month; prop.year = year; prop.teacher = teacher; prop.city = city; prop.country = country; entity.uvpMap.put(unverifiedProgramId, prop); Member.populateDependantFields(client, Utils.getList(entity)); ofy(client).save().entity(entity).now(); Member.populateDependantFields(client, Utils.getList(entity)); List<UnverifiedProgramProp> list = new ArrayList<>(entity.uvpMap.values()); Collections.sort(list); return list; } public static List<UnverifiedProgramProp> deleteUnverifiedProgram(String client, long memberId, int unverifiedProgramTypeId, String login) { Client.ensureValid(client); MemberEntity entity = MemberLoader.safeGet(client, memberId, login); // this also checks if (login is same as member) or (login is a valid user) if (!entity.uvpMap.containsKey(unverifiedProgramTypeId)) throw new APIException().status(Status.ERROR_RESOURCE_NOT_FOUND).message( "No unverified program [" + unverifiedProgramTypeId + "] for member [" + memberId + "]"); entity.uvpMap.remove(unverifiedProgramTypeId); populateDependantFields(client, Utils.getList(entity)); ofy(client).save().entity(entity).now(); List<UnverifiedProgramProp> list = new ArrayList<>(entity.uvpMap.values()); Collections.sort(list); return list; } public static void rebuild(MemberQueryCondition mqc, String login) { ensureNotNull(mqc, "mqc is null"); Client.ensureValid(mqc.client); User.ensureValidUser(mqc.client, login); final int MAX_MEMBERS = 10000; int count = MemberLoader.getCount(mqc, login); if (count > MAX_MEMBERS) throw new APIException("Attempt to rebuild [" + count + "] member entities. Max allowed is [" + MAX_MEMBERS + "]").status(Status.ERROR_OVERFLOW); List<MemberEntity> memberEntities = MemberLoader.queryEntities(mqc, login); rebuild(mqc.client, memberEntities); } private static void rebuild(String client, List<MemberEntity> memberEntities) { Client.ensureValid(client); ensureNoNullElement(memberEntities); removeDeadReferences(client, memberEntities); populateDependantFields(client, memberEntities); ofy(client).save().entities(memberEntities); } private static void removeDeadReferences(String client, List<MemberEntity> memberEntities) { Client.ensureValid(client); ensureNoNullElement(memberEntities); Set<Long> programIds = new HashSet<>(); Set<Long> listIds = new HashSet<>(); for (MemberEntity memberEntity : memberEntities) { programIds.addAll(memberEntity.programIds); listIds.addAll(memberEntity.listIds); } Map<Long, ProgramEntity> programMap = Program.getEntities(client, programIds); Map<Long, ListEntity> listMap = crmdna.list.List.get(client, listIds); Set<Long> missingProgramIds = programIds; missingProgramIds.removeAll(programMap.keySet()); Set<Long> missingListIds = listIds; missingListIds.removeAll(listMap.keySet()); for (MemberEntity memberEntity : memberEntities) { memberEntity.programIds.removeAll(missingProgramIds); memberEntity.listIds.removeAll(missingListIds); } } public static BulkSubscriptionResultProp bulkSubscribeList(String client, long listId, MailMap mailMap, String login) { //This will add the emails to list and subscribe them to group provided they are not unsubscribed. Client.ensureValid(client); ListProp listProp = crmdna.list.List.safeGet(client, listId).toProp(); Group.safeGet(client, listProp.groupId); if (!listProp.enabled) throw new APIException("Cannot add emails to disabled list [" + listId + "]") .status(Status.ERROR_PRECONDITION_FAILED); ensureNotNull(mailMap, "mailMap is null"); User.ensureGroupLevelPrivilege(client, listProp.groupId, login, GroupLevelPrivilege.UPDATE_LIST); ensure(mailMap.size() != 0, "mailMap is empty"); ensure(mailMap.size() < 5001, "Attempt to add [" + mailMap.size() + "] emails to list [" + listId + "] in one shot. Max allowed in one shot is [5000]. Please split up into multiple batches"); Set<String> allEmails = mailMap.getEmails(); List<MemberEntity> existingMemberEntities = ofy(client).load().type(MemberEntity.class).filter("email in", allEmails).list(); BulkSubscriptionResultProp result = new BulkSubscriptionResultProp(); for (MemberEntity memberEntity : existingMemberEntities) { ensureNotNull(memberEntity.email); result.existingMemberEmails.add(memberEntity.email); } result.newMemberEmails.addAll(allEmails); result.newMemberEmails.removeAll(result.existingMemberEmails); List<MemberEntity> toSave = new ArrayList<>(); for (MemberEntity memberEntity : existingMemberEntities) { ensureNotNull(memberEntity.email); boolean changed = memberEntity.listIds.add(listId); if (changed) { toSave.add(memberEntity); result.addedToListEmails.add(memberEntity.email); } //subscribe to group if not already unsubscribed if (! memberEntity.unsubscribedGroupIds.contains(listProp.groupId)) { boolean changed2 = memberEntity.subscribedGroupIds.add(listProp.groupId); if (changed2) { toSave.add(memberEntity); result.addedToGroupSubscriptionEmails.add(memberEntity.email); } else { result.alreadySubscribedToGroupEmails.add(memberEntity.email); } } else { result.alreadyUnsubscribedToGroupEmails.add(memberEntity.email); } } ensure(allEmails.size() >= result.newMemberEmails.size()); List<MemberEntity> newMemberEntities = MemberFactory.create(client, result.newMemberEmails.size()); ensureEqual(result.newMemberEmails.size(), newMemberEntities.size()); int i = 0; for (String email : result.newMemberEmails) { MemberEntity memberEntity = newMemberEntities.get(i); memberEntity.email = email.toLowerCase(); memberEntity.firstName = mailMap.get(MailMap.MergeVarID.FIRST_NAME, email); memberEntity.lastName = mailMap.get(MailMap.MergeVarID.LAST_NAME, email); memberEntity.groupIds.add(listProp.groupId); memberEntity.asOfYYYYMMDD = DateUtils.toYYYYMMDD(new Date()); memberEntity.listIds.add(listId); memberEntity.subscribedGroupIds.add(listProp.groupId); result.addedToListEmails.add(email); result.addedToGroupSubscriptionEmails.add(email); toSave.add(memberEntity); result.newMemberEmails.add(email); i = i + 1; } populateDependantFields(client, toSave); ofy(client).save().entities(toSave).now(); return result; } public static String getCSV(String client, List<MemberProp> memberProps) { Client.ensureValid(client); StringBuilder sb = new StringBuilder(); sb.append("First Name, Last Name, Email, Mobile, Home Phone, Office Phone, Occupation, " + "Company Name, Programs, Practices, Member ID\n"); if (memberProps == null || memberProps.isEmpty()) return sb.toString(); for (MemberProp memberProp : memberProps) { sb.append(Utils.csvEncode(memberProp.contact.firstName)).append(','); sb.append(Utils.csvEncode(memberProp.contact.lastName)).append(','); sb.append(Utils.csvEncode(memberProp.contact.email)).append(','); sb.append(Utils.csvEncode(memberProp.contact.mobilePhone)).append(','); sb.append(Utils.csvEncode(memberProp.contact.homePhone)).append(','); sb.append(Utils.csvEncode(memberProp.contact.officePhone)).append(','); sb.append(Utils.csvEncode(memberProp.contact.occupation)).append(','); sb.append(Utils.csvEncode(memberProp.contact.company)).append(','); StringBuilder sbPrograms = new StringBuilder(); for (Iterator<MemberProgramProp> iter = memberProp.memberProgramProps.iterator(); iter.hasNext(); ) { MemberProgramProp memberProgramProp = iter.next(); if (memberProgramProp.programType.contains("Sathsang")) continue; if (!memberProgramProp.verified) sb.append('<'); sbPrograms.append(memberProgramProp.programType.replaceAll(" ", "")).append('_'); sbPrograms.append(memberProgramProp.month).append('-'); sbPrograms.append(memberProgramProp.year).append('-'); if (memberProgramProp.teacher != null) { sbPrograms.append(memberProgramProp.teacher.replaceAll(" ", "")); } else { sbPrograms.append("NA"); } if (!memberProgramProp.verified) sbPrograms.append('>'); if (iter.hasNext()) sbPrograms.append("\r\n"); } sb.append(Utils.csvEncode(sbPrograms.toString())).append(','); StringBuilder sbPractices = new StringBuilder(); for (Iterator<String> iter = memberProp.practices.iterator(); iter.hasNext(); ) { String practice = iter.next(); sbPractices.append(practice.replaceAll(" ", "")); if (iter.hasNext()) sbPractices.append("\r\n"); } sb.append(Utils.csvEncode(sbPractices.toString())).append(','); sb.append(memberProp.memberId); sb.append("\r\n"); } return sb.toString(); } //This is used for datamigration for group level subscription. This method should probably be removed after data migration is complete //returns the number of entities that changed public static Set<Long> populateSubUnsubGroupIds(String client, Set<Long> memberIds, String login) { Client.ensureValid(client); ensure(UserCore.isSuperUser(login), "This function can be called only by a super user"); Map<Long, MemberEntity> memberIdVsEntity = MemberLoader.get(client, memberIds, login); Set<Long> allListIds = new HashSet<>(); for (Map.Entry<Long, MemberEntity> e : memberIdVsEntity.entrySet()) { allListIds.addAll(e.getValue().toProp().subscribedListIds); allListIds.addAll(e.getValue().toProp().unsubscribedListIds); } Map<Long, ListEntity> listIdVsEntity = crmdna.list.List.get(client, allListIds); List<MemberEntity> toSave = new ArrayList<>(); Set<Long> changedMemberIds = new HashSet<>(); for (Map.Entry<Long, MemberEntity> e : memberIdVsEntity.entrySet()) { MemberEntity memberEntity = e.getValue(); boolean changed = false; Set<Long> subscribedGroupIds = new HashSet<>(); for (long listId : memberEntity.subscribedListIds) { if (listIdVsEntity.containsKey(listId)) { long groupId = listIdVsEntity.get(listId).toProp().groupId; subscribedGroupIds.add(groupId); } } Set<Long> unsubscribedGroupIds = new HashSet<>(); for (long listId : memberEntity.unsubscribedListIds) { if (listIdVsEntity.containsKey(listId)) { long groupId = listIdVsEntity.get(listId).toProp().groupId; unsubscribedGroupIds.add(groupId); //remove subscription if any subscribedGroupIds.remove(groupId); } } if (memberEntity.subscribedGroupIds.addAll(subscribedGroupIds)) { changed = true; } if (memberEntity.unsubscribedGroupIds.addAll(unsubscribedGroupIds)) { changed = true; } if (memberEntity.subscribedGroupIds.removeAll(memberEntity.unsubscribedGroupIds)) { changed = true; } if (memberEntity.listIds.addAll(memberEntity.subscribedListIds)) { changed = true; } if (memberEntity.listIds.addAll(memberEntity.unsubscribedListIds)) { changed = true; } if (changed) { toSave.add(memberEntity); changedMemberIds.add(memberEntity.getId()); } } ofy(client).save().entities(toSave).now(); return changedMemberIds; } public enum AccountType { FEDERATED, GOOGLE } }