/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Cyclos 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package nl.strohalm.cyclos.services.elements;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
import nl.strohalm.cyclos.access.AdminAdminPermission;
import nl.strohalm.cyclos.access.AdminMemberPermission;
import nl.strohalm.cyclos.access.BrokerPermission;
import nl.strohalm.cyclos.access.MemberPermission;
import nl.strohalm.cyclos.dao.access.UserDAO;
import nl.strohalm.cyclos.dao.access.UsernameChangeLogDAO;
import nl.strohalm.cyclos.dao.groups.GroupHistoryLogDAO;
import nl.strohalm.cyclos.dao.members.ElementDAO;
import nl.strohalm.cyclos.dao.members.NotificationPreferenceDAO;
import nl.strohalm.cyclos.dao.members.PendingEmailChangeDAO;
import nl.strohalm.cyclos.dao.members.PendingMemberDAO;
import nl.strohalm.cyclos.dao.members.RegistrationAgreementLogDAO;
import nl.strohalm.cyclos.entities.Entity;
import nl.strohalm.cyclos.entities.Relationship;
import nl.strohalm.cyclos.entities.access.Channel;
import nl.strohalm.cyclos.entities.access.Channel.Credentials;
import nl.strohalm.cyclos.entities.access.Channel.Principal;
import nl.strohalm.cyclos.entities.access.MemberUser;
import nl.strohalm.cyclos.entities.access.OperatorUser;
import nl.strohalm.cyclos.entities.access.PrincipalType;
import nl.strohalm.cyclos.entities.access.User;
import nl.strohalm.cyclos.entities.access.UsernameChangeLog;
import nl.strohalm.cyclos.entities.accounts.Account;
import nl.strohalm.cyclos.entities.accounts.AccountType;
import nl.strohalm.cyclos.entities.accounts.MemberAccount;
import nl.strohalm.cyclos.entities.accounts.MemberAccountType;
import nl.strohalm.cyclos.entities.accounts.MemberGroupAccountSettings;
import nl.strohalm.cyclos.entities.accounts.cards.Card;
import nl.strohalm.cyclos.entities.accounts.loans.Loan;
import nl.strohalm.cyclos.entities.accounts.loans.LoanParameters;
import nl.strohalm.cyclos.entities.accounts.loans.LoanQuery;
import nl.strohalm.cyclos.entities.accounts.transactions.Invoice;
import nl.strohalm.cyclos.entities.accounts.transactions.InvoiceQuery;
import nl.strohalm.cyclos.entities.accounts.transactions.Payment;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferType;
import nl.strohalm.cyclos.entities.ads.AdQuery;
import nl.strohalm.cyclos.entities.customization.fields.MemberCustomField;
import nl.strohalm.cyclos.entities.customization.fields.MemberCustomFieldValue;
import nl.strohalm.cyclos.entities.exceptions.EntityNotFoundException;
import nl.strohalm.cyclos.entities.exceptions.UnexpectedEntityException;
import nl.strohalm.cyclos.entities.groups.AdminGroup;
import nl.strohalm.cyclos.entities.groups.BrokerGroup;
import nl.strohalm.cyclos.entities.groups.Group;
import nl.strohalm.cyclos.entities.groups.GroupFilter;
import nl.strohalm.cyclos.entities.groups.GroupHistoryLog;
import nl.strohalm.cyclos.entities.groups.GroupQuery;
import nl.strohalm.cyclos.entities.groups.MemberGroup;
import nl.strohalm.cyclos.entities.groups.MemberGroupSettings;
import nl.strohalm.cyclos.entities.groups.OperatorGroup;
import nl.strohalm.cyclos.entities.members.Administrator;
import nl.strohalm.cyclos.entities.members.BrokeringQuery;
import nl.strohalm.cyclos.entities.members.Element;
import nl.strohalm.cyclos.entities.members.ElementQuery;
import nl.strohalm.cyclos.entities.members.FullTextAdminQuery;
import nl.strohalm.cyclos.entities.members.FullTextElementQuery;
import nl.strohalm.cyclos.entities.members.FullTextMemberQuery;
import nl.strohalm.cyclos.entities.members.FullTextOperatorQuery;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.entities.members.MemberQuery;
import nl.strohalm.cyclos.entities.members.Operator;
import nl.strohalm.cyclos.entities.members.PendingEmailChange;
import nl.strohalm.cyclos.entities.members.PendingMember;
import nl.strohalm.cyclos.entities.members.PendingMemberQuery;
import nl.strohalm.cyclos.entities.members.RegisteredMember;
import nl.strohalm.cyclos.entities.members.RegistrationAgreement;
import nl.strohalm.cyclos.entities.members.RegistrationAgreementLog;
import nl.strohalm.cyclos.entities.members.adInterests.AdInterestQuery;
import nl.strohalm.cyclos.entities.members.brokerings.Brokering;
import nl.strohalm.cyclos.entities.members.messages.Message.Type;
import nl.strohalm.cyclos.entities.members.preferences.NotificationPreference;
import nl.strohalm.cyclos.entities.members.remarks.GroupRemark;
import nl.strohalm.cyclos.entities.services.ServiceClient;
import nl.strohalm.cyclos.entities.settings.AccessSettings;
import nl.strohalm.cyclos.entities.settings.AccessSettings.UsernameGeneration;
import nl.strohalm.cyclos.entities.settings.LocalSettings;
import nl.strohalm.cyclos.entities.settings.MessageSettings;
import nl.strohalm.cyclos.exceptions.MailSendingException;
import nl.strohalm.cyclos.exceptions.PermissionDeniedException;
import nl.strohalm.cyclos.services.access.AccessServiceLocal;
import nl.strohalm.cyclos.services.access.ChannelServiceLocal;
import nl.strohalm.cyclos.services.access.exceptions.NotConnectedException;
import nl.strohalm.cyclos.services.accounts.AccountDateDTO;
import nl.strohalm.cyclos.services.accounts.AccountServiceLocal;
import nl.strohalm.cyclos.services.accounts.MemberAccountHandler;
import nl.strohalm.cyclos.services.accounts.cards.CardServiceLocal;
import nl.strohalm.cyclos.services.accounts.pos.PosServiceLocal;
import nl.strohalm.cyclos.services.ads.AdServiceLocal;
import nl.strohalm.cyclos.services.customization.AdminCustomFieldServiceLocal;
import nl.strohalm.cyclos.services.customization.MemberCustomFieldServiceLocal;
import nl.strohalm.cyclos.services.customization.OperatorCustomFieldServiceLocal;
import nl.strohalm.cyclos.services.elements.exceptions.MemberHasBalanceException;
import nl.strohalm.cyclos.services.elements.exceptions.MemberHasOpenInvoicesException;
import nl.strohalm.cyclos.services.elements.exceptions.MemberHasPendingLoansException;
import nl.strohalm.cyclos.services.elements.exceptions.RegistrationAgreementNotAcceptedException;
import nl.strohalm.cyclos.services.elements.exceptions.UsernameAlreadyInUseException;
import nl.strohalm.cyclos.services.fetch.FetchServiceLocal;
import nl.strohalm.cyclos.services.groups.GroupServiceLocal;
import nl.strohalm.cyclos.services.permissions.PermissionServiceLocal;
import nl.strohalm.cyclos.services.preferences.PreferenceServiceLocal;
import nl.strohalm.cyclos.services.settings.SettingsServiceLocal;
import nl.strohalm.cyclos.services.transactions.InvoiceServiceLocal;
import nl.strohalm.cyclos.services.transactions.LoanServiceLocal;
import nl.strohalm.cyclos.services.transactions.PaymentServiceLocal;
import nl.strohalm.cyclos.services.transactions.ScheduledPaymentServiceLocal;
import nl.strohalm.cyclos.utils.CacheCleaner;
import nl.strohalm.cyclos.utils.CustomFieldHelper;
import nl.strohalm.cyclos.utils.DateHelper;
import nl.strohalm.cyclos.utils.ElementVO;
import nl.strohalm.cyclos.utils.EntityHelper;
import nl.strohalm.cyclos.utils.HashHandler;
import nl.strohalm.cyclos.utils.MailHandler;
import nl.strohalm.cyclos.utils.MessageProcessingHelper;
import nl.strohalm.cyclos.utils.MessageResolver;
import nl.strohalm.cyclos.utils.Pair;
import nl.strohalm.cyclos.utils.Period;
import nl.strohalm.cyclos.utils.PropertyHelper;
import nl.strohalm.cyclos.utils.RangeConstraint;
import nl.strohalm.cyclos.utils.RelationshipHelper;
import nl.strohalm.cyclos.utils.StringHelper;
import nl.strohalm.cyclos.utils.TimePeriod;
import nl.strohalm.cyclos.utils.access.LoggedUser;
import nl.strohalm.cyclos.utils.access.PermissionHelper;
import nl.strohalm.cyclos.utils.lock.UniqueObjectHandler;
import nl.strohalm.cyclos.utils.notifications.AdminNotificationHandler;
import nl.strohalm.cyclos.utils.notifications.MemberNotificationHandler;
import nl.strohalm.cyclos.utils.query.QueryParameters.ResultType;
import nl.strohalm.cyclos.utils.transaction.CurrentTransactionData;
import nl.strohalm.cyclos.utils.validation.DelegatingValidator;
import nl.strohalm.cyclos.utils.validation.EmailValidation;
import nl.strohalm.cyclos.utils.validation.GeneralValidation;
import nl.strohalm.cyclos.utils.validation.LengthValidation;
import nl.strohalm.cyclos.utils.validation.PropertyValidation;
import nl.strohalm.cyclos.utils.validation.RequiredError;
import nl.strohalm.cyclos.utils.validation.UniqueError;
import nl.strohalm.cyclos.utils.validation.ValidationError;
import nl.strohalm.cyclos.utils.validation.ValidationException;
import nl.strohalm.cyclos.utils.validation.Validator;
import nl.strohalm.cyclos.utils.validation.Validator.Property;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
/**
* Implementation for element service
* @author luis
*/
public class ElementServiceImpl implements ElementServiceLocal {
private static enum ActivationMail {
IGNORE, THREADED, ONLINE
}
/**
* Validates that the element's username is not already taken
* @author luis
*/
private class ExistingUsernameValidation implements GeneralValidation {
private static final long serialVersionUID = -3358417537796698704L;
@Override
public ValidationError validate(final Object object) {
String username = null;
if (object instanceof Element) {
username = ((Element) object).getUsername();
} else if (object instanceof PendingMember) {
username = ((PendingMember) object).getUsername();
}
final Long id = ((Entity) object).getId();
if (StringUtils.isEmpty(username)) {
return null;
}
boolean existing = false;
if (object instanceof Operator) {
Member member;
if (LoggedUser.isOperator()) { // an operator modifying his own profile
member = (Member) LoggedUser.accountOwner();
} else {
member = LoggedUser.element();
}
try {
final OperatorUser existingOperator = userDao.loadOperator(member, username);
existing = !existingOperator.getId().equals(id);
} catch (final EntityNotFoundException e) {
// not found. ok
}
} else {
// Search in Elements
try {
final User existingUser = userDao.load(username);
existing = !existingUser.getId().equals(id);
} catch (final EntityNotFoundException e) {
// not found. ok
}
if (!existing) {
// Search in PendingMembers
try {
final PendingMember pendingMember = pendingMemberDao.loadByUsername(username);
if (object instanceof PendingMember && pendingMember.getId().equals(((PendingMember) object).getId())) {
// Updating the same pending member. Don't consider as existing
existing = false;
} else {
existing = true;
}
} catch (final EntityNotFoundException e) {
// not found. ok
}
}
}
if (existing) {
// If it got to this point, an user with the given username already exists
return new ValidationError("createMember.error.usernameAlreadyInUse", username);
}
return null;
}
}
private class PendingMemberEmailValidation implements PropertyValidation {
private final PendingMember pendingMember;
private static final long serialVersionUID = 377970183876523686L;
private PendingMemberEmailValidation(final PendingMember pendingMember) {
this.pendingMember = pendingMember;
}
@Override
public ValidationError validate(final Object object, final Object property, final Object value) {
final String email = (String) value;
LocalSettings localSettings = settingsService.getLocalSettings();
if (localSettings.isEmailUnique()) {
if (StringUtils.isNotEmpty(email) && pendingMemberDao.emailExists(pendingMember, email)) {
return new UniqueError(email);
}
try {
final Element loaded = elementDao.loadByEmail(email);
if (loaded != null) {
return new UniqueError(email);
}
} catch (final EntityNotFoundException e) {
// Ok, no one had that e-mail
}
}
return null;
}
}
private class UniqueEmailValidation implements PropertyValidation {
private static final long serialVersionUID = -1170302387628372503L;
private final Long userId;
private final boolean pendingToo;
private UniqueEmailValidation(final Long userId, final boolean pendingToo) {
this.userId = userId;
this.pendingToo = pendingToo;
}
@Override
public ValidationError validate(final Object object, final Object property, final Object value) {
final String email = (String) value;
if (StringUtils.isEmpty(email)) {
return null;
}
try {
final Element loaded = elementDao.loadByEmail(email);
if (userId == null) {
// member is new, not yet persisted element, so always wrong if the email already exists
return new UniqueError(email);
}
if (!loaded.getId().equals(userId)) {
return new UniqueError(email);
}
} catch (final EntityNotFoundException e) {
// Ok, no one had that e-mail
}
if (pendingToo && pendingMemberDao.emailExists(null, email)) {
// The e-mail is used by a PendingMember
return new UniqueError(email);
}
return null;
}
}
private MessageResolver messageResolver = new MessageResolver.NoOpMessageResolver();
private ElementDAO elementDao;
private GroupHistoryLogDAO groupHistoryLogDao;
private NotificationPreferenceDAO notificationPreferenceDao;
private UserDAO userDao;
private AccessServiceLocal accessService;
private AccountServiceLocal accountService;
private AdInterestServiceLocal adInterestService;
private AdServiceLocal adService;
private BrokeringServiceLocal brokeringService;
private CommissionServiceLocal commissionService;
private ContactServiceLocal contactService;
private AdminCustomFieldServiceLocal adminCustomFieldService;
private MemberCustomFieldServiceLocal memberCustomFieldService;
private OperatorCustomFieldServiceLocal operatorCustomFieldService;
private FetchServiceLocal fetchService;
private GroupServiceLocal groupService;
private InvoiceServiceLocal invoiceService;
private PaymentServiceLocal paymentService;
private LoanServiceLocal loanService;
private ScheduledPaymentServiceLocal scheduledPaymentService;
private PreferenceServiceLocal preferenceService;
private RemarkServiceLocal remarkService;
private SettingsServiceLocal settingsService;
private ChannelServiceLocal channelService;
private HashHandler hashHandler;
private MailHandler mailHandler;
private MemberAccountHandler memberAccountHandler;
private PendingMemberDAO pendingMemberDao;
private PendingEmailChangeDAO pendingEmailChangeDao;
private RegistrationAgreementLogDAO registrationAgreementLogDao;
private CardServiceLocal cardService;
private PosServiceLocal posService;
private PermissionServiceLocal permissionService;
private UsernameChangeLogDAO usernameChangeLogDao;
private AdminNotificationHandler adminNotificationHandler;
private MemberNotificationHandler memberNotificationHandler;
private CustomFieldHelper customFieldHelper;
private UniqueObjectHandler uniqueObjectHandler;
@Override
public void acceptAgreement(final String remoteAddress) {
Member member = LoggedUser.member();
MemberGroup group = member.getMemberGroup();
RegistrationAgreement registrationAgreement = group.getRegistrationAgreement();
if (registrationAgreement == null) {
throw new ValidationException();
}
createAgreementLog(remoteAddress, member, registrationAgreement);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public boolean applyQueryRestrictions(final FullTextElementQuery query) {
if (query instanceof FullTextAdminQuery) {
// Only admins with permissions can search admins
permissionService.permission().admin(AdminAdminPermission.ADMINS_VIEW).check();
} else if (query instanceof FullTextOperatorQuery) {
// Only members and operators can see other operators from the member himself
Member member = ((FullTextOperatorQuery) query).getMember();
if (member == null) {
throw new ValidationException();
}
permissionService.permission(member).member().operator().check();
} else if (query instanceof FullTextMemberQuery) {
// For members, just enforce the visible groups
Collection<Group> visibleGroups = (Collection) permissionService.getVisibleMemberGroups(false);
if (visibleGroups.isEmpty()) {
return false;
}
Collection<Group> queryGroups = (Collection<Group>) query.getGroups();
query.setGroups(PermissionHelper.checkSelection(visibleGroups, queryGroups));
}
// Ensure that only enabled users will be returned
boolean isMemberSearchingForOperators = (query instanceof FullTextOperatorQuery) && LoggedUser.element().equals(((FullTextOperatorQuery) query).getMember());
if (!LoggedUser.isAdministrator() && !isMemberSearchingForOperators) {
query.setEnabled(true);
}
return true;
}
@SuppressWarnings("unchecked")
@Override
public BulkMemberActionResultVO bulkChangeMemberChannels(final FullTextMemberQuery query, Collection<Channel> enableChannels, Collection<Channel> disableChannels) throws ValidationException {
validateBulkChangeChannels(query, enableChannels, disableChannels);
// load the channels to ensure they are valid ones
enableChannels = channelService.load(EntityHelper.toIds(enableChannels));
disableChannels = channelService.load(EntityHelper.toIds(disableChannels));
int changed = 0, unchanged = 0;
// force the result type to ITERATOR to avoid load all members in memory
query.setIterateAll();
final List<Member> members = (List<Member>) fullTextSearch(query);
CacheCleaner cacheCleaner = new CacheCleaner(fetchService);
for (Member member : members) {
member = fetchService.fetch(member, Member.Relationships.CHANNELS, Element.Relationships.GROUP);
boolean mustChange = false;
for (Channel channel : enableChannels) {
if (accessService.isChannelAllowedToBeEnabledForMember(channel, member) && !member.getChannels().contains(channel)) {
mustChange = true;
member.getChannels().add(channel);
}
}
Collection<Channel> memberChannels = accessService.getChannelsEnabledForMember(member);
Collection<Channel> memberDisabledChannels = CollectionUtils.subtract(channelService.list(), memberChannels);
Collection<Channel> toDisableChannels = CollectionUtils.subtract(disableChannels, memberDisabledChannels);
if (CollectionUtils.isNotEmpty(toDisableChannels)) {
mustChange = true;
// at this point disableChannels contains only actually enabled member channels
for (Channel channel : toDisableChannels) {
member.getChannels().remove(channel);
}
}
if (mustChange) {
changed++;
elementDao.update(member);
} else {
unchanged++;
}
cacheCleaner.clearCache();
}
return new BulkMemberActionResultVO(changed, unchanged);
}
@Override
@SuppressWarnings("unchecked")
public BulkMemberActionResultVO bulkChangeMemberGroup(final FullTextMemberQuery query, MemberGroup newGroup, final String comments) throws ValidationException {
if (newGroup == null || newGroup.isTransient()) {
throw new ValidationException();
}
if (StringUtils.isEmpty(comments)) {
throw new ValidationException();
}
newGroup = fetchService.fetch(newGroup);
int changed = 0;
int unchanged = 0;
// force the result type to ITERATOR to avoid load all members in memory
query.setIterateAll();
final List<Member> members = (List<Member>) fullTextSearch(query);
CacheCleaner cacheCleaner = new CacheCleaner(fetchService);
for (final Member member : members) {
if (newGroup.equals(member.getGroup())) {
unchanged++;
} else {
changeGroup(member, newGroup, comments);
changed++;
}
cacheCleaner.clearCache();
}
return new BulkMemberActionResultVO(changed, unchanged);
}
@Override
public <E extends Element> E changeGroup(E element, Group newGroup, final String comments) throws MemberHasBalanceException, MemberHasOpenInvoicesException, ValidationException {
newGroup = fetchService.fetch(newGroup);
// Validate the arguments
final Element loggedElement = LoggedUser.element();
if (element == null || newGroup == null || StringUtils.isEmpty(comments) || loggedElement.equals(element)) {
throw new ValidationException();
}
// Check the current group
element = load(element.getId(), Element.Relationships.USER, Element.Relationships.GROUP);
final Group oldGroup = element.getGroup();
if (oldGroup.equals(newGroup) || oldGroup.getStatus() == Group.Status.REMOVED) {
throw new ValidationException();
}
if (element instanceof Member) {
checkNewGroup((Member) element, (MemberGroup) newGroup);
}
if (newGroup.getStatus() == Group.Status.REMOVED) {
// Disconnect the user if he is logged in
try {
accessService.disconnect(element.getUser());
} catch (final NotConnectedException e) {
// Ok - not logged in
}
if (element instanceof Member) {
Member member = (Member) element;
// Remove all ads
final AdQuery adQuery = new AdQuery();
adQuery.setOwner(member);
adService.remove(EntityHelper.toIds(adService.search(adQuery)));
// Remove all ad interests
final AdInterestQuery adInterestQuery = new AdInterestQuery();
adInterestQuery.setOwner(member);
adInterestService.remove(EntityHelper.toIds(adInterestService.search(adInterestQuery)));
// Remove all notification preferences
notificationPreferenceDao.delete(EntityHelper.toIds(notificationPreferenceDao.load(member)));
// Remove all contacts
contactService.remove(EntityHelper.toIds(contactService.list(member)));
// Cancel all cards
cardService.cancelAllMemberCards(member);
// Unassign all pos
posService.unassignAllMemberPos(member);
}
}
boolean noLongerBroker = false;
if (element instanceof Member) {
Member member = (Member) element;
MemberGroup oldMemberGroup = (MemberGroup) oldGroup;
MemberGroup newMemberGroup = (MemberGroup) newGroup;
// Remove all brokerings if the member is no longer a broker
noLongerBroker = oldMemberGroup.isBroker() && (newGroup.getStatus() == Group.Status.REMOVED || !newMemberGroup.isBroker());
if (noLongerBroker) {
final BrokeringQuery brokeringQuery = new BrokeringQuery();
brokeringQuery.setBroker(member);
brokeringQuery.setResultType(ResultType.ITERATOR);
final MessageSettings messageSettings = settingsService.getMessageSettings();
String brokeringComments = messageSettings.getBrokerRemovedRemarkComments();
brokeringComments = MessageProcessingHelper.processVariables(brokeringComments, member, settingsService.getLocalSettings());
for (final Brokering brokering : brokeringService.search(brokeringQuery)) {
brokeringService.remove(brokering, brokeringComments);
}
}
// Update broker commissions
if (oldMemberGroup.isBroker() || newMemberGroup.isBroker()) {
commissionService.updateBrokerCommissions(member, oldMemberGroup, newMemberGroup);
}
}
// Update the group
element.setGroup(newGroup);
elementDao.update(element);
// Handle the member accounts
if (element instanceof Member) {
Member member = (Member) element;
final boolean wasInactive = !member.isActive();
handleAccounts(member);
if (wasInactive && member.isActive()) {
sendActivationMailIfNeeded(ActivationMail.ONLINE, member);
}
}
// Creates the group remark and updates group history logs
createGroupRemark(element, oldGroup, newGroup, comments);
// Update the index
elementDao.addToIndex(element);
// Notify if was a broker and no longer is
if (element instanceof Member && noLongerBroker) {
Member member = (Member) element;
memberNotificationHandler.removedFromBrokerGroupNotification(member);
}
return element;
}
@Override
public Member changeMemberProfileByWebService(final ServiceClient client, final Member member) {
if (member.isTransient()) {
throw new UnexpectedEntityException();
}
final Element current = load(member.getId());
final Set<MemberGroup> manageGroups = fetchService.fetch(client, ServiceClient.Relationships.MANAGE_GROUPS).getManageGroups();
if (!manageGroups.contains(current.getGroup())) {
throw new PermissionDeniedException();
}
return save(member, ActivationMail.THREADED, WhenSaving.PROFILE, false);
}
@Override
public <E extends Element> E changeProfile(final E element) {
return save(element, null, WhenSaving.PROFILE, false);
}
@Override
public PendingEmailChange confirmEmailChange(final String key) throws EntityNotFoundException {
Member member = LoggedUser.member();
PendingEmailChange pendingEmailChange = pendingEmailChangeDao.getByMember(member);
if (pendingEmailChange == null) {
throw new EntityNotFoundException(PendingEmailChange.class);
}
member.setEmail(pendingEmailChange.getNewEmail());
elementDao.update(member);
pendingEmailChangeDao.removeAll(member);
return pendingEmailChange;
}
@Override
public void createAgreementForAllMembers(final RegistrationAgreement registrationAgreement, final MemberGroup group) {
elementDao.createAgreementForAllMembers(registrationAgreement, group);
}
@Override
@SuppressWarnings("unchecked")
public List<? extends Element> fullTextSearch(final FullTextElementQuery query) {
// Since the full text does not index group filters, only groups, disassemble the group filters into groups
if (query instanceof FullTextMemberQuery) {
final FullTextMemberQuery memberQuery = (FullTextMemberQuery) query;
// groupFilters
final Collection<GroupFilter> groupFilters = fetchService.fetch(memberQuery.getGroupFilters(), GroupFilter.Relationships.GROUPS);
if (CollectionUtils.isNotEmpty(groupFilters)) {
final boolean hasGroupFilters = CollectionUtils.isNotEmpty(memberQuery.getGroupFilters());
final boolean hasGroups = CollectionUtils.isNotEmpty(memberQuery.getGroups());
final Set<Group> groupsFromFilters = new HashSet<Group>();
if (hasGroupFilters) {
// Get all groups from selected group filters
for (final GroupFilter groupFilter : groupFilters) {
groupsFromFilters.addAll(groupFilter.getGroups());
}
if (hasGroups) {
// When there's both groups and group filters, use an intersection between them
memberQuery.setGroups(CollectionUtils.intersection(groupsFromFilters, memberQuery.getGroups()));
} else {
// Else, use only the groups from group filters
memberQuery.setGroups(groupsFromFilters);
}
}
memberQuery.setGroupFilters(null);
}
}
if (query.getNameDisplay() == null) {
query.setNameDisplay(settingsService.getLocalSettings().getMemberResultDisplay());
}
query.setAnalyzer(settingsService.getLocalSettings().getLanguage().getAnalyzer());
return elementDao.fullTextSearch(query);
}
@Override
public ElementVO getElementVO(final long id) {
return id > 0 ? elementDao.load(id).readOnlyView() : null;
}
@Override
public Calendar getFirstMemberActivationDate() {
return elementDao.getFirstMemberActivationDate();
}
@Override
public PendingEmailChange getPendingEmailChange(final Member member) {
return pendingEmailChangeDao.getByMember(member);
}
@Override
public List<? extends Group> getPossibleNewGroups(final Element element) {
Group group = fetchService.fetch(element, Element.Relationships.GROUP).getGroup();
if (group.getStatus() == Group.Status.REMOVED) {
return Collections.singletonList(group);
}
final GroupQuery query = new GroupQuery();
if (group instanceof OperatorGroup) {
query.setNatures(Group.Nature.OPERATOR);
query.setMember(((OperatorGroup) group).getMember());
} else {
if (group.getNature() == Group.Nature.ADMIN) {
query.setNatures(Group.Nature.ADMIN);
} else {
query.setNatures(Group.Nature.MEMBER, Group.Nature.BROKER);
}
}
final List<? extends Group> groups = groupService.search(query);
groups.remove(group);
return groups;
}
@Override
public Member insertMember(final Member member, final boolean ignoreActivationMail, final boolean validatePassword) {
final ActivationMail activationMail = ignoreActivationMail ? ActivationMail.IGNORE : ActivationMail.THREADED;
return save(member, activationMail, WhenSaving.IMPORT, false);
}
@Override
public void invitePerson(final String email) {
mailHandler.sendInvitation(LoggedUser.element(), email);
}
@Override
public <T extends Element> T load(final Long id, final Relationship... fetch) {
final T element = elementDao.<T> load(id, fetch);
checkAccessToMember(element);
return element;
}
@Override
public Member loadByPrincipal(final PrincipalType principalType, String principal, final Relationship... fetch) {
try {
if (principal == null) {
throw new NullPointerException();
}
final Principal principalEnum = principalType == null ? Principal.USER : principalType.getPrincipal();
switch (principalEnum) {
case USER:
final User user = loadUser(principal);
return (Member) fetchService.fetch(user.getElement(), fetch);
case EMAIL:
return (Member) elementDao.loadByEmail(principal, fetch);
case CUSTOM_FIELD:
final MemberCustomField customField = principalType.getCustomField();
if (StringUtils.isNotEmpty(customField.getPattern())) {
principal = StringHelper.removeMask(customField.getPattern(), principal, false);
}
return elementDao.loadByCustomField(customField, principal, fetch);
case CARD:
// Use numbers only, avoid conflicts with formatting
final String cardNumber = StringHelper.onlyNumbers(principal);
final Card card = cardService.loadByNumber(new BigInteger(cardNumber), Card.Relationships.OWNER);
final Calendar expirationDate = DateHelper.truncateNextDay(card.getExpirationDate());
final boolean cardExpired = !Calendar.getInstance().getTime().before(expirationDate.getTime());
if (card.getStatus() != Card.Status.ACTIVE || cardExpired) {
throw new EntityNotFoundException("The card " + cardNumber + " is not active or has expired.");
}
return fetchService.fetch(card.getOwner(), fetch);
}
throw new EntityNotFoundException(Member.class);
} catch (final EntityNotFoundException e) {
throw e;
} catch (final Exception e) {
final EntityNotFoundException enfe = new EntityNotFoundException(Member.class);
enfe.initCause(e);
throw enfe;
}
}
@Override
public OperatorUser loadOperatorUser(final Member member, final String operatorUsername, final Relationship... fetch) throws EntityNotFoundException {
return userDao.loadOperator(member, operatorUsername, fetch);
}
@Override
public PendingMember loadPendingMember(final Long id, final Relationship... fetch) {
final PendingMember pendingMember = pendingMemberDao.load(id, fetch);
checkManagement(pendingMember);
return pendingMember;
}
@Override
public PendingMember loadPendingMemberByKey(final String key, final Relationship... fetch) {
return pendingMemberDao.loadByKey(key, fetch);
}
@Override
public <T extends User> T loadUser(final Long id, final Relationship... fetch) {
final T user = userDao.<T> load(id, fetch);
checkAccessToMember(user.getElement());
return user;
}
@Override
public <T extends User> T loadUser(final String username, final Relationship... fetch) {
final T user = userDao.<T> load(username, fetch);
checkAccessToMember(user.getElement());
return user;
}
@Override
@SuppressWarnings("unchecked")
public int processMembersExpirationForGroups(final Calendar time) {
// Find on member groups...
final GroupQuery query = new GroupQuery();
query.setNatures(Group.Nature.MEMBER, Group.Nature.BROKER);
int count = 0;
final List<Group> groups = (List<Group>) groupService.search(query);
final String message = messageResolver.message("changeGroup.member.expired");
for (final Group group : groups) {
final MemberGroup memberGroup = (MemberGroup) fetchService.fetch(group);
final MemberGroupSettings memberSettings = memberGroup.getMemberSettings();
final TimePeriod expireMembersAfter = memberSettings.getExpireMembersAfter();
// ... those who expire members after a given time period ...
if (expireMembersAfter == null || expireMembersAfter.getNumber() <= 0) {
continue;
}
final Calendar limit = expireMembersAfter.remove(DateHelper.truncate(time));
final List<Member> members = elementDao.listMembersRegisteredBeforeOnGroup(limit, memberGroup);
final MemberGroup groupAfterExpiration = memberSettings.getGroupAfterExpiration();
// ... then expire members on that group
for (final Member member : members) {
changeGroup(member, groupAfterExpiration, message);
count++;
}
}
return count;
}
@Override
public Member publicValidateRegistration(final String key) throws EntityNotFoundException, RegistrationAgreementNotAcceptedException {
final PendingMember pendingMember = pendingMemberDao.loadByKey(key, PendingMember.Relationships.values());
// Store the agreement data
final RegistrationAgreement registrationAgreement = pendingMember.getRegistrationAgreement();
final Calendar agreementDate = pendingMember.getRegistrationAgreementDate();
final String remoteAddress = pendingMember.getRemoteAddress();
// Only proceed if the agreement (if any) has already been accepted
final MemberGroup group = pendingMember.getMemberGroup();
if (group.getRegistrationAgreement() != null && !group.getRegistrationAgreement().equals(registrationAgreement)) {
throw new RegistrationAgreementNotAcceptedException();
}
// Eagerly delete to avoid the username being reported as already used
pendingMemberDao.delete(pendingMember.getId());
// Translate the PendingMember into a Member
Member member = new Member();
PropertyHelper.copyProperties(pendingMember, member, "id", "memberGroup", "username", "password", "customValues");
member.setGroup(pendingMember.getMemberGroup());
final MemberUser user = new MemberUser();
member.setUser(user);
user.setSalt(pendingMember.getSalt());
user.setUsername(pendingMember.getUsername());
final String password = pendingMember.getPassword();
if (StringUtils.isNotEmpty(password)) {
user.setPassword(password);
if (!pendingMember.isForceChangePassword()) {
user.setPasswordDate(Calendar.getInstance());
}
}
// copy CF
customFieldHelper.cloneFieldValues(pendingMember, member);
member = save(member, ActivationMail.ONLINE, WhenSaving.EMAIL_VALIDATION, pendingMember.isForceChangePassword());
// Mark the agreement as accepted
if (registrationAgreement != null) {
final RegistrationAgreementLog log = new RegistrationAgreementLog();
log.setMember(member);
log.setRegistrationAgreement(registrationAgreement);
log.setDate(agreementDate);
log.setRemoteAddress(remoteAddress);
registrationAgreementLogDao.insert(log);
}
adminNotificationHandler.notifyNewPublicRegistration(member);
return member;
}
@Override
public void purgeOldEmailValidations(final Calendar time) {
final LocalSettings localSettings = settingsService.getLocalSettings();
final TimePeriod timePeriod = localSettings.getDeletePendingRegistrationsAfter();
if (timePeriod == null || timePeriod.getNumber() <= 0) {
return;
}
Calendar limit = timePeriod.remove(time);
pendingMemberDao.deleteBefore(limit);
pendingEmailChangeDao.deleteBefore(limit);
}
@Override
public Object register(final Element element, final boolean forceChangePassword, final String remoteAddress) {
if (element instanceof Administrator) {
if (!forceChangePassword) {
element.getUser().setPasswordDate(Calendar.getInstance());
}
return save(element, ActivationMail.ONLINE, WhenSaving.ADMIN_BY_ADMIN, forceChangePassword);
} else if (element instanceof Member) {
WhenSaving whenSaving;
if (LoggedUser.getAccessType() == null) {
whenSaving = WhenSaving.PUBLIC;
} else if (LoggedUser.isBroker()) {
whenSaving = WhenSaving.BY_BROKER;
} else {
whenSaving = WhenSaving.MEMBER_BY_ADMIN;
}
return register((Member) element, whenSaving, forceChangePassword, remoteAddress);
} else if (element instanceof Operator) {
Operator operator = (Operator) element;
final Member loggedMember = LoggedUser.element();
operator.setMember(loggedMember);
operator.getUser().setPasswordDate(Calendar.getInstance());
return save(operator, null, WhenSaving.OPERATOR, false);
}
// If not an admin, member or operator, what is it?
throw new UnexpectedEntityException();
}
@Override
public RegisteredMember registerMemberByWebService(ServiceClient client, final Member member, final String remoteAddress) {
client = fetchService.fetch(client, ServiceClient.Relationships.MANAGE_GROUPS);
final Set<MemberGroup> manageGroups = client.getManageGroups();
if (manageGroups.isEmpty()) {
throw new PermissionDeniedException();
}
MemberGroup group;
try {
group = (MemberGroup) fetchService.fetch(member.getGroup());
} catch (final Exception e) {
throw new EntityNotFoundException();
}
if (group == null) {
group = manageGroups.iterator().next();
}
return register(member, WhenSaving.WEB_SERVICE, false, remoteAddress);
}
@Override
public <T extends User> T reloadUser(final Long id, final Relationship... fetch) {
final T user = userDao.<T> reload(id, fetch);
checkAccessToMember(user.getElement());
return user;
}
@Override
public void remove(final Long id) throws UnexpectedEntityException {
final Element element = load(id);
if (element instanceof Member) {
final Member member = (Member) element;
if (member.getActivationDate() != null) {
// Cannot permanently remove an active member
throw new UnexpectedEntityException();
}
}
elementDao.delete(id);
elementDao.removeFromIndex(element);
}
@Override
public int removePendingMembers(final Long... ids) {
if (ids == null) {
return 0;
}
for (final Long id : ids) {
checkManagement(EntityHelper.reference(PendingMember.class, id));
}
return pendingMemberDao.delete(ids);
}
@Override
public PendingMember resendEmail(final PendingMember pendingMember) throws MailSendingException {
// Send the mail
try {
mailHandler.sendEmailValidation(pendingMember);
pendingMember.setLastEmailDate(Calendar.getInstance());
return pendingMemberDao.update(pendingMember);
} finally {
if (CurrentTransactionData.hasMailError()) {
throw new MailSendingException("Email validation for " + pendingMember.getName());
}
}
}
@Override
public PendingEmailChange resendEmailChange(final Long memberId) throws MailSendingException {
Element element = load(memberId);
if (!(element instanceof Member)) {
throw new EntityNotFoundException(Member.class);
}
Member member = (Member) element;
PendingEmailChange change = pendingEmailChangeDao.getByMember(member);
return resendEmail(change);
}
@Override
public List<? extends Element> search(final ElementQuery query) {
query.fetch(Element.Relationships.USER);
if (query.getOrder() == null) {
query.setOrder(settingsService.getLocalSettings().getMemberResultDisplay());
}
return elementDao.search(query);
}
@Override
public List<PendingMember> search(final PendingMemberQuery params) {
Collection<MemberGroup> allowedGroups = null;
if (LoggedUser.hasUser()) {
if (LoggedUser.isBroker()) {
final Member loggedBroker = LoggedUser.element();
params.setBroker(loggedBroker);
final BrokerGroup group = LoggedUser.group();
allowedGroups = fetchService.fetch(group, BrokerGroup.Relationships.POSSIBLE_INITIAL_GROUPS).getPossibleInitialGroups();
} else {
final AdminGroup group = LoggedUser.group();
allowedGroups = fetchService.fetch(group, AdminGroup.Relationships.MANAGES_GROUPS).getManagesGroups();
}
}
if (allowedGroups != null) {
if (allowedGroups.isEmpty()) {
// No allowed group
return Collections.emptyList();
}
// Ensure only the allowed groups are returned
final Collection<MemberGroup> groups = params.getGroups();
if (CollectionUtils.isEmpty(groups)) {
params.setGroups(allowedGroups);
} else {
for (final Iterator<MemberGroup> iterator = groups.iterator(); iterator.hasNext();) {
final MemberGroup memberGroup = iterator.next();
if (!allowedGroups.contains(memberGroup)) {
iterator.remove();
}
}
}
}
return pendingMemberDao.search(params);
}
@Override
public List<? extends Element> searchAtDate(final MemberQuery query, final Calendar date) {
if (query.getOrder() == null) {
query.setOrder(settingsService.getLocalSettings().getMemberResultDisplay());
}
return elementDao.searchAtDate(query, date);
}
public void setAccessServiceLocal(final AccessServiceLocal accessService) {
this.accessService = accessService;
}
public void setAccountServiceLocal(final AccountServiceLocal accountService) {
this.accountService = accountService;
}
public void setAdInterestServiceLocal(final AdInterestServiceLocal adInterestService) {
this.adInterestService = adInterestService;
}
public void setAdminCustomFieldServiceLocal(final AdminCustomFieldServiceLocal adminCustomFieldService) {
this.adminCustomFieldService = adminCustomFieldService;
}
public void setAdminNotificationHandler(final AdminNotificationHandler adminNotificationHandler) {
this.adminNotificationHandler = adminNotificationHandler;
}
public void setAdServiceLocal(final AdServiceLocal adService) {
this.adService = adService;
}
public void setBrokeringServiceLocal(final BrokeringServiceLocal brokeringService) {
this.brokeringService = brokeringService;
}
public void setCardServiceLocal(final CardServiceLocal cardService) {
this.cardService = cardService;
}
public void setChannelServiceLocal(final ChannelServiceLocal channelService) {
this.channelService = channelService;
}
public void setCommissionServiceLocal(final CommissionServiceLocal commissionService) {
this.commissionService = commissionService;
}
public void setContactServiceLocal(final ContactServiceLocal contactService) {
this.contactService = contactService;
}
public void setCustomFieldHelper(final CustomFieldHelper customFieldHelper) {
this.customFieldHelper = customFieldHelper;
}
public void setElementDao(final ElementDAO elementDAO) {
elementDao = elementDAO;
}
public void setFetchServiceLocal(final FetchServiceLocal fetchService) {
this.fetchService = fetchService;
}
public void setGroupHistoryLogDao(final GroupHistoryLogDAO groupHistoryLogDao) {
this.groupHistoryLogDao = groupHistoryLogDao;
}
public void setGroupServiceLocal(final GroupServiceLocal groupService) {
this.groupService = groupService;
}
public void setHashHandler(final HashHandler hashHandler) {
this.hashHandler = hashHandler;
}
public void setInvoiceServiceLocal(final InvoiceServiceLocal invoiceService) {
this.invoiceService = invoiceService;
}
public void setLoanServiceLocal(final LoanServiceLocal loanService) {
this.loanService = loanService;
}
public void setMailHandler(final MailHandler mailHandler) {
this.mailHandler = mailHandler;
}
public void setMemberAccountHandler(final MemberAccountHandler memberAccountHandler) {
this.memberAccountHandler = memberAccountHandler;
}
public void setMemberCustomFieldServiceLocal(final MemberCustomFieldServiceLocal memberCustomFieldService) {
this.memberCustomFieldService = memberCustomFieldService;
}
public void setMemberNotificationHandler(final MemberNotificationHandler memberNotificationHandler) {
this.memberNotificationHandler = memberNotificationHandler;
}
public void setMessageResolver(final MessageResolver messageResolver) {
this.messageResolver = messageResolver;
}
public void setNotificationPreferenceDao(final NotificationPreferenceDAO notificationPreferenceDao) {
this.notificationPreferenceDao = notificationPreferenceDao;
}
public void setOperatorCustomFieldServiceLocal(final OperatorCustomFieldServiceLocal operatorCustomFieldService) {
this.operatorCustomFieldService = operatorCustomFieldService;
}
public void setPaymentServiceLocal(final PaymentServiceLocal paymentService) {
this.paymentService = paymentService;
}
public void setPendingEmailChangeDao(final PendingEmailChangeDAO pendingEmailChangeDao) {
this.pendingEmailChangeDao = pendingEmailChangeDao;
}
public void setPendingMemberDao(final PendingMemberDAO pendingMemberDao) {
this.pendingMemberDao = pendingMemberDao;
}
public void setPermissionServiceLocal(final PermissionServiceLocal permissionService) {
this.permissionService = permissionService;
}
public void setPosServiceLocal(final PosServiceLocal posService) {
this.posService = posService;
}
public void setPreferenceServiceLocal(final PreferenceServiceLocal preferenceService) {
this.preferenceService = preferenceService;
}
@Override
public void setRegistrationAgreementAgreed(PendingMember pendingMember) {
pendingMember = fetchService.reload(pendingMember);
RegistrationAgreement registrationAgreement = pendingMember.getMemberGroup().getRegistrationAgreement();
if (registrationAgreement == null) {
throw new ValidationException();
}
pendingMember.setRegistrationAgreement(registrationAgreement);
pendingMember.setRegistrationAgreementDate(Calendar.getInstance());
pendingMemberDao.update(pendingMember);
}
public void setRegistrationAgreementLogDao(final RegistrationAgreementLogDAO registrationAgreementLogDao) {
this.registrationAgreementLogDao = registrationAgreementLogDao;
}
public void setRemarkServiceLocal(final RemarkServiceLocal remarkService) {
this.remarkService = remarkService;
}
public void setScheduledPaymentServiceLocal(final ScheduledPaymentServiceLocal scheduledPaymentService) {
this.scheduledPaymentService = scheduledPaymentService;
}
public void setSettingsServiceLocal(final SettingsServiceLocal settingsService) {
this.settingsService = settingsService;
}
public void setUniqueObjectHandler(final UniqueObjectHandler uniqueObjectHandler) {
this.uniqueObjectHandler = uniqueObjectHandler;
}
public void setUserDao(final UserDAO userDAO) {
userDao = userDAO;
}
public void setUsernameChangeLogDao(final UsernameChangeLogDAO usernameChangeLogDao) {
this.usernameChangeLogDao = usernameChangeLogDao;
}
@Override
public boolean shallAcceptAgreement(Member member) {
member = fetchService.fetch(member, Element.Relationships.GROUP);
final RegistrationAgreement registrationAgreement = member.getMemberGroup().getRegistrationAgreement();
if (registrationAgreement == null) {
return false;
}
final List<RegistrationAgreementLog> logs = registrationAgreementLogDao.listByMember(member);
for (final RegistrationAgreementLog log : logs) {
if (log.getRegistrationAgreement().equals(registrationAgreement)) {
// Already accepted
return false;
}
}
// Not accepted yet
return true;
}
@Override
public PendingMember update(PendingMember pendingMember) {
if (pendingMember == null || pendingMember.isTransient()) {
throw new UnexpectedEntityException();
}
checkManagement(pendingMember);
validate(pendingMember);
final Collection<MemberCustomFieldValue> customValues = pendingMember.getCustomValues();
pendingMember = pendingMemberDao.update(pendingMember);
pendingMember.setCustomValues(customValues);
memberCustomFieldService.saveValues(pendingMember);
return pendingMember;
}
@Override
public void validate(final Element element, final WhenSaving when, final boolean manualPassword) throws ValidationException {
Group group = element.getGroup();
// We need a group in order to validate
if (group == null || group.isTransient()) {
if (element.isTransient()) {
// Cannot validate a new member without a group
throw new ValidationException("group", "member.group", new RequiredError());
} else {
// If no new group is supplied, just keep the old group
final Element loaded = load(element.getId(), Element.Relationships.GROUP);
group = loaded.getGroup();
// Put the old group back on the element
element.setGroup(group);
}
} else {
group = fetchService.fetch(group);
}
if (element instanceof Member && element.isPersistent()) {
// We must fill in the custom values by their default values if the member cannot edit it, so validation won't fail
final Member member = (Member) element;
final Collection<MemberCustomFieldValue> customValues = member.getCustomValues();
List<MemberCustomField> fields = memberCustomFieldService.list();
fields = customFieldHelper.onlyForGroup(fields, (MemberGroup) group);
final Member current = (Member) load(element.getId(), Member.Relationships.CUSTOM_VALUES);
final Collection<MemberCustomFieldValue> currentValues = current.getCustomValues();
final Element loggedElement = LoggedUser.hasUser() ? LoggedUser.element() : null;
final boolean byOwner = loggedElement != null && loggedElement.equals(current);
boolean byBroker = false;
if (loggedElement != null && LoggedUser.isBroker()) {
byBroker = loggedElement.equals(current.getBroker());
}
final Group loggedGroup = LoggedUser.hasUser() ? LoggedUser.group() : null;
for (final MemberCustomField field : fields) {
if (loggedGroup != null && !field.getUpdateAccess().granted(loggedGroup, byOwner, byBroker, false, when == WhenSaving.WEB_SERVICE)) {
final MemberCustomFieldValue currentValue = customFieldHelper.findByField(field, currentValues);
final MemberCustomFieldValue value = customFieldHelper.findByField(field, customValues);
if (value != null) {
customValues.remove(value);
}
if (currentValue != null) {
customValues.add(currentValue);
}
}
}
}
createValidator(group, element, when, manualPassword).validate(element);
}
@Override
public void validate(final PendingMember pendingMember) throws ValidationException {
getValidator(pendingMember).validate(pendingMember);
}
@Override
@SuppressWarnings("unchecked")
public void validateBulkChangeChannels(final FullTextMemberQuery query, final Collection<Channel> enableChannels, final Collection<Channel> disableChannels) {
Collection<Channel> intersection = CollectionUtils.intersection(enableChannels, disableChannels);
if (CollectionUtils.isNotEmpty(intersection)) {
throw new ValidationException("changeChannels.invalidChannelsSelection", toString(intersection));
}
}
private void cancelScheduledPaymentsAndNotify(final Member member, final MemberGroup newGroup) {
Collection<MemberAccountType> accountTypes = newGroup.getAccountTypes();
if (accountTypes == null) {
accountTypes = Collections.emptyList();
}
scheduledPaymentService.cancelScheduledPaymentsAndNotify(member, accountTypes);
}
private void checkAccessToMember(final Element element) {
if (element instanceof Member && LoggedUser.hasUser()) {
final Member member = fetchService.fetch((Member) element, Member.Relationships.BROKER);
if ((LoggedUser.isMember() || LoggedUser.isOperator())) {
final Member loggedMember = (Member) LoggedUser.accountOwner();
if (!loggedMember.equals(member)) {
final MemberGroup group = fetchService.fetch(loggedMember.getMemberGroup(), MemberGroup.Relationships.CAN_VIEW_PROFILE_OF_GROUPS);
final Collection<MemberGroup> canViewMembersOfGroups = group.getCanViewProfileOfGroups();
if (!canViewMembersOfGroups.contains(element.getGroup()) && !loggedMember.equals(member.getBroker())) {
throw new PermissionDeniedException();
}
}
} else if (LoggedUser.isAdministrator()) {
final AdminGroup group = LoggedUser.group();
final Collection<MemberGroup> managesGroups = fetchService.fetch(group, AdminGroup.Relationships.MANAGES_GROUPS).getManagesGroups();
if (!managesGroups.contains(member.getGroup())) {
throw new PermissionDeniedException();
}
}
}
}
private void checkManagement(PendingMember pendingMember) {
boolean valid = false;
if (LoggedUser.hasUser()) {
pendingMember = fetchService.fetch(pendingMember);
if (LoggedUser.isBroker()) {
final Member loggedBroker = LoggedUser.element();
valid = loggedBroker.equals(pendingMember.getBroker());
} else {
final AdminGroup group = LoggedUser.group();
final Collection<MemberGroup> managesGroups = fetchService.reload(group, AdminGroup.Relationships.MANAGES_GROUPS).getManagesGroups();
valid = managesGroups.contains(pendingMember.getMemberGroup());
}
}
if (!valid) {
throw new PermissionDeniedException();
}
}
private void checkNewGroup(final Member member, final MemberGroup newGroup) {
Collection<MemberAccountType> accountTypes = newGroup.getAccountTypes();
if (accountTypes == null) {
accountTypes = Collections.emptyList();
}
// Check if the member has any open loans
final LoanQuery lQuery = new LoanQuery();
lQuery.fetch(RelationshipHelper.nested(Loan.Relationships.TRANSFER, Payment.Relationships.TYPE));
lQuery.setStatus(Loan.Status.OPEN);
lQuery.setMember(member);
AccountType accType;
for (final Loan loan : loanService.search(lQuery)) {
final LoanParameters params = loan.getTransferType().getLoan();
if (!accountTypes.contains(getFrom(params.getRepaymentType()))) {
throw new MemberHasPendingLoansException(newGroup);
}
if (params.getType() == Loan.Type.WITH_INTEREST) {
if ((accType = getFrom(params.getMonthlyInterestRepaymentType())) != null && !accountTypes.contains(accType)) {
throw new MemberHasPendingLoansException(newGroup);
}
if ((accType = getFrom(params.getGrantFeeRepaymentType())) != null && !accountTypes.contains(accType)) {
throw new MemberHasPendingLoansException(newGroup);
}
if ((accType = getFrom(params.getExpirationFeeRepaymentType())) != null && !accountTypes.contains(accType)) {
throw new MemberHasPendingLoansException(newGroup);
}
if ((accType = getFrom(params.getExpirationDailyInterestRepaymentType())) != null && !accountTypes.contains(accType)) {
throw new MemberHasPendingLoansException(newGroup);
}
}
}
// Check if the member has any open invoices
final InvoiceQuery invoiceQuery = new InvoiceQuery();
invoiceQuery.setDirection(InvoiceQuery.Direction.INCOMING);
invoiceQuery.setOwner(member);
invoiceQuery.setStatus(Invoice.Status.OPEN);
for (final Invoice invoice : invoiceService.search(invoiceQuery)) {
boolean found = false;
final Iterator<MemberAccountType> accIt = accountTypes.iterator();
while (!found && accIt.hasNext()) {
accType = accIt.next();
final Iterator<TransferType> ttIt = accType.getFromTransferTypes().iterator();
while (!found && ttIt.hasNext()) {
final TransferType tt = ttIt.next();
if (tt.getTo().equals(invoice.getDestinationAccountType())) {
found = true;
}
}
}
if (!found) {
throw new MemberHasOpenInvoicesException(newGroup);
}
}
invoiceQuery.setDirection(InvoiceQuery.Direction.OUTGOING);
for (final Invoice invoice : invoiceService.search(invoiceQuery)) {
if (!accountTypes.contains(invoice.getDestinationAccountType())) {
throw new MemberHasOpenInvoicesException(newGroup);
}
}
// Cancel all incoming and outgoing scheduled payments and notify payers/receivers
// We cancel the payments here and not in the changeGroup method because we must check the balance after the cancellation
// to ensure we take in to account the reserved amount (if any) that will be returned to the from account.
cancelScheduledPaymentsAndNotify(member, newGroup);
// Check the account balance
final BigDecimal minimumPayment = paymentService.getMinimumPayment();
for (final Account account : accountService.getAccounts(member)) {
final BigDecimal balance = accountService.getBalance(new AccountDateDTO(account));
if (!accountTypes.contains(account.getType()) && (balance.abs().compareTo(minimumPayment) > 0)) {
throw new MemberHasBalanceException(newGroup, (MemberAccountType) account.getType());
}
}
}
private RegistrationAgreementLog createAgreementLog(final String remoteAddress, final Member member, final RegistrationAgreement registrationAgreement) {
final RegistrationAgreementLog log = new RegistrationAgreementLog();
log.setMember(member);
log.setRegistrationAgreement(registrationAgreement);
log.setDate(Calendar.getInstance());
log.setRemoteAddress(remoteAddress);
return registrationAgreementLogDao.insert(log);
}
private void createGroupHistoryLog(final Element element, final Group group, final Calendar start) {
final GroupHistoryLog newGhl = new GroupHistoryLog();
newGhl.setElement(element);
newGhl.setGroup(group);
newGhl.setPeriod(Period.begginingAt(start));
groupHistoryLogDao.insert(newGhl);
}
/**
* Create a remark for a group change
*/
private void createGroupRemark(final Element member, final Group oldGroup, final Group newGroup, final String comments) {
final Calendar now = Calendar.getInstance();
final GroupRemark remark = new GroupRemark();
if (LoggedUser.hasUser()) {
remark.setWriter(LoggedUser.element());
}
remark.setSubject(member);
remark.setDate(now);
remark.setOldGroup(oldGroup);
remark.setNewGroup(newGroup);
remark.setComments(comments);
remarkService.save(remark);
updateGroupHistoryLogs(member, newGroup, now);
}
/**
* Creates a validator for the given group
*/
private Validator createValidator(final Group group, final Element element, final WhenSaving when, final boolean manualPassword) {
final Element.Nature nature = group.getNature().getElementNature();
final String baseName = nature.name().toLowerCase();
return new DelegatingValidator(new DelegatingValidator.DelegateSource() {
@Override
public Validator getValidator() {
final AccessSettings accessSettings = settingsService.getAccessSettings();
final LocalSettings localSettings = settingsService.getLocalSettings();
final Validator validator = new Validator(baseName);
validator.property("group").required();
validator.property("name").required().maxLength(100);
final boolean isMember = nature == Element.Nature.MEMBER;
ServiceClient client = LoggedUser.serviceClient();
// Validate username
if ((element.isTransient() && (!isMember || accessSettings.getUsernameGeneration() == UsernameGeneration.NONE)) || element.isPersistent()) {
final Property username = validator.property("username");
username.required();
// Checks that the username is not yet used
validator.general(new ExistingUsernameValidation());
final RangeConstraint usernameLength = accessSettings.getUsernameLength();
if (usernameLength != null) {
username.add(new LengthValidation(usernameLength)).regex(accessSettings.getUsernameRegex());
}
}
if (element.isTransient()) {
// When manual password or public registration, login password is always required
boolean loginPasswordRequired = manualPassword || when == WhenSaving.PUBLIC;
if (client != null) {
// For service clients, if the channel uses the login password, it is required as well
final Channel channel = client.getChannel();
if (channel.getCredentials() == Credentials.DEFAULT || channel.getCredentials() == Credentials.LOGIN_PASSWORD) {
loginPasswordRequired = true;
}
}
// Validate the password on insert
final Property password = validator.property("user.password").key("createMember.password");
if (loginPasswordRequired) {
password.required();
}
// We can only validate the password if it's not pre-hashed
if (!when.isPreHashed()) {
accessService.addLoginPasswordValidation(element, password);
}
// Validate the pin, if any
if (isMember) {
boolean pinRequired = false;
if (client != null) {
// For service clients, if the channel uses the login password, it is required as well
final Channel channel = client.getChannel();
if (channel.getCredentials() == Credentials.PIN) {
pinRequired = true;
}
}
final Property pin = validator.property("user.pin").key("channel.credentials.PIN");
if (pinRequired) {
pin.required();
}
if (!when.isPreHashed()) {
accessService.addPinValidation((Member) element, pin);
}
}
}
// Validate the email
final Property email = validator.property("email");
// Email is not required for operators nor service clients which are set to ignore validation
final boolean ignoreValidation = nature == Element.Nature.OPERATOR || client != null && client.isIgnoreRegistrationValidations();
if (!ignoreValidation && localSettings.isEmailRequired()) {
email.required();
}
email.add(EmailValidation.instance()).maxLength(100);
if (localSettings.isEmailUnique()) {
email.add(new UniqueEmailValidation(element.getId(), when == WhenSaving.PUBLIC));
}
// Custom fields
validator.chained(new DelegatingValidator(new DelegatingValidator.DelegateSource() {
@Override
public Validator getValidator() {
switch (nature) {
case ADMIN:
return adminCustomFieldService.getValueValidator((AdminGroup) group);
case MEMBER:
Member member = (Member) element;
MemberCustomField.Access access = null;
if (!LoggedUser.hasUser()) {
access = MemberCustomField.Access.REGISTRATION;
} else {
member = fetchService.fetch(member, Member.Relationships.BROKER);
final Element loggedElement = LoggedUser.element();
if (loggedElement.equals(element)) {
access = MemberCustomField.Access.MEMBER;
} else if ((member == null && LoggedUser.isBroker()) || (member != null && loggedElement.equals(member.getBroker()))) {
access = MemberCustomField.Access.BROKER;
} else if (loggedElement instanceof Administrator) {
access = MemberCustomField.Access.ADMIN;
}
}
return memberCustomFieldService.getValueValidator((MemberGroup) group, access);
case OPERATOR:
return operatorCustomFieldService.getValueValidator(((OperatorGroup) group).getMember());
}
return null;
}
}));
return validator;
}
});
}
/**
* Generate a member username
*/
private String generateUsername(final int length) {
String generated;
boolean exists;
do {
// Generate a random number
generated = RandomStringUtils.randomNumeric(length);
if (generated.charAt(0) == '0') {
// The first character cannot be zero
generated = (new Random().nextInt(8) + 1) + generated.substring(1);
}
// Check if such username exists
try {
userDao.load(generated);
exists = true;
} catch (final EntityNotFoundException e) {
exists = false;
}
} while (exists);
return generated;
}
private nl.strohalm.cyclos.entities.groups.MemberGroupSettings.EmailValidation getEmailValidation(final WhenSaving whenSaving, final Element element) {
if (whenSaving == null || element.getNature() != Element.Nature.MEMBER) {
return null;
}
switch (whenSaving) {
case BY_BROKER:
return nl.strohalm.cyclos.entities.groups.MemberGroupSettings.EmailValidation.BROKER;
case MEMBER_BY_ADMIN:
return nl.strohalm.cyclos.entities.groups.MemberGroupSettings.EmailValidation.ADMIN;
case PROFILE:
if (LoggedUser.serviceClient() != null) {
return nl.strohalm.cyclos.entities.groups.MemberGroupSettings.EmailValidation.WEB_SERVICE;
} else if (LoggedUser.element().equals(element)) {
return nl.strohalm.cyclos.entities.groups.MemberGroupSettings.EmailValidation.USER;
} else if (LoggedUser.isBroker()) {
return nl.strohalm.cyclos.entities.groups.MemberGroupSettings.EmailValidation.BROKER;
} else if (LoggedUser.isAdministrator()) {
return nl.strohalm.cyclos.entities.groups.MemberGroupSettings.EmailValidation.ADMIN;
}
case PUBLIC:
return nl.strohalm.cyclos.entities.groups.MemberGroupSettings.EmailValidation.USER;
case WEB_SERVICE:
return nl.strohalm.cyclos.entities.groups.MemberGroupSettings.EmailValidation.WEB_SERVICE;
}
return null;
}
private AccountType getFrom(final TransferType tt) {
return tt == null ? null : tt.getFrom();
}
private Validator getValidator(final PendingMember pendingMember) {
final AccessSettings accessSettings = settingsService.getAccessSettings();
final MemberGroup group = pendingMember.getMemberGroup();
final Validator validator = new Validator("member");
validator.property("name").required().maxLength(100);
if (accessSettings.getUsernameGeneration() == UsernameGeneration.NONE) {
validator.property("username").required().maxLength(64);
}
validator.property("email").required().maxLength(100).add(new PendingMemberEmailValidation(pendingMember));
if (group != null) {
validator.chained(new DelegatingValidator(new DelegatingValidator.DelegateSource() {
@Override
public Validator getValidator() {
MemberCustomField.Access access = null;
if (!LoggedUser.hasUser()) {
access = MemberCustomField.Access.REGISTRATION;
} else {
if (LoggedUser.element().equals(pendingMember.getBroker())) {
access = MemberCustomField.Access.BROKER;
} else {
access = MemberCustomField.Access.ADMIN;
}
}
return memberCustomFieldService.getValueValidator(group, access);
}
}));
}
validator.general(new ExistingUsernameValidation());
return validator;
}
/**
* This method creates the accounts related to the member group, and marks those not related as inactive
*/
@SuppressWarnings("unchecked")
private void handleAccounts(Member member) {
member = fetchService.fetch(member, RelationshipHelper.nested(Element.Relationships.GROUP, MemberGroup.Relationships.ACCOUNT_SETTINGS));
final MemberGroup group = member.getMemberGroup();
final Collection<MemberGroupAccountSettings> accountSettings = group.getAccountSettings();
final List<MemberAccount> accounts = (List<MemberAccount>) accountService.getAccounts(member, Account.Relationships.TYPE);
// Mark as inactive the accounts no longer used
for (final MemberAccount account : accounts) {
if (!hasAccount(account, accountSettings)) {
memberAccountHandler.deactivate(account, group.getStatus() == Group.Status.REMOVED);
}
}
// Create the accounts the member does not yet has
if (!CollectionUtils.isEmpty(accountSettings)) {
for (final MemberGroupAccountSettings settings : accountSettings) {
memberAccountHandler.activate(member, settings.getAccountType());
}
}
// Activate members without accounts but in active groups
if (member.getActivationDate() == null && group.isActive()) {
member.setActivationDate(Calendar.getInstance());
}
}
/**
* Check if the specified account belongs to any of the accountSettings
*/
private boolean hasAccount(final MemberAccount account, final Collection<MemberGroupAccountSettings> accountSettings) {
for (final MemberGroupAccountSettings settings : accountSettings) {
if (account.getType().equals(settings.getAccountType())) {
return true;
}
}
return false;
}
private RegisteredMember register(Member member, final WhenSaving when, final boolean forceChangePassword, final String remoteAddress) {
final MemberGroup group = (MemberGroup) fetchService.fetch(member.getGroup());
member.setGroup(group);
RegisteredMember result;
// Check the mail validation
final MemberGroupSettings settings = group.getMemberSettings();
nl.strohalm.cyclos.entities.groups.MemberGroupSettings.EmailValidation emailValidation = getEmailValidation(when, member);
final boolean validateEmail = settings.getEmailValidation() != null && settings.getEmailValidation().contains(emailValidation);
if (validateEmail) {
// It's enabled: Save a pending member
final PendingMember pendingMember = new PendingMember();
PropertyHelper.copyProperties(member, pendingMember);
pendingMember.setCreationDate(Calendar.getInstance());
pendingMember.setSalt(hashHandler.newSalt());
pendingMember.setForceChangePassword(forceChangePassword);
final User user = member.getUser();
if (user != null) {
pendingMember.setPassword(hashHandler.hash(pendingMember.getSalt(), user.getPassword()));
}
if (user instanceof MemberUser) {
final MemberUser memberUser = (MemberUser) user;
pendingMember.setPin(hashHandler.hash(pendingMember.getSalt(), memberUser.getPin()));
}
pendingMember.setValidationKey(RandomStringUtils.randomAlphanumeric(64));
if (when == WhenSaving.PUBLIC) {
// On public registrations, the license agreement has been accepted
final RegistrationAgreement registrationAgreement = group.getRegistrationAgreement();
if (registrationAgreement != null) {
pendingMember.setRegistrationAgreement(registrationAgreement);
pendingMember.setRegistrationAgreementDate(Calendar.getInstance());
}
}
validate(pendingMember);
result = pendingMemberDao.insert(pendingMember);
memberCustomFieldService.saveValues(result);
resendEmail((PendingMember) result);
} else {
// Not enabled: save the member directly
final ActivationMail activationMail = when == WhenSaving.WEB_SERVICE ? ActivationMail.THREADED : ActivationMail.ONLINE;
result = member = save(member, activationMail, when, forceChangePassword);
if (when == WhenSaving.PUBLIC) {
// On the public registration, when there's an agreement, store it
member = fetchService.fetch(member, RelationshipHelper.nested(Element.Relationships.GROUP, MemberGroup.Relationships.REGISTRATION_AGREEMENT));
final RegistrationAgreement registrationAgreement = member.getMemberGroup().getRegistrationAgreement();
if (registrationAgreement != null) {
createAgreementLog(remoteAddress, member, registrationAgreement);
}
}
// Notify the admins
adminNotificationHandler.notifyNewPublicRegistration(member);
}
return result;
}
private PendingEmailChange resendEmail(final PendingEmailChange pendingEmailChange) throws MailSendingException {
// Send the mail
try {
mailHandler.sendEmailChange(pendingEmailChange);
pendingEmailChange.setLastEmailDate(Calendar.getInstance());
return pendingEmailChangeDao.update(pendingEmailChange);
} finally {
if (CurrentTransactionData.hasMailError()) {
throw new MailSendingException("Email change validation for " + pendingEmailChange.getMember().getName());
}
}
}
/**
* Saves the given element
*/
@SuppressWarnings("unchecked")
private <E extends Element> E save(E element, final ActivationMail activationMail, final WhenSaving when, final boolean forceChangePassword) {
validate(element, when, false);
// Store the custom values on a saparate collection
final Collection<?> values = (Collection<?>) PropertyHelper.get(element, "customValues");
PropertyHelper.set(element, "customValues", null);
final boolean isInsert = element.isTransient();
if (isInsert) {
// Check if we must generate a username for member
final AccessSettings accessSettings = settingsService.getAccessSettings();
final UsernameGeneration usernameGeneration = accessSettings.getUsernameGeneration();
if (element instanceof Member && usernameGeneration != UsernameGeneration.NONE) {
User user = element.getUser();
// Assign a new user if none found
if (user == null) {
user = new MemberUser();
element.setUser(user);
}
// Generate the username
String generated = generateUsername(accessSettings.getGeneratedUsernameLength());
while (!uniqueObjectHandler.tryAcquire(Pair.<Object, Object> of(generated, generated))) {
generated = generateUsername(accessSettings.getGeneratedUsernameLength());
}
user.setUsername(generated);
} else {
// Check if the username already in use
final String username = element.getUsername();
if (!uniqueObjectHandler.tryAcquire(Pair.<Object, Object> of(username, username))) {
throw new UsernameAlreadyInUseException(username);
}
try {
if (element instanceof Operator) {
loadOperatorUser((Member) LoggedUser.element(), username);
} else {
loadUser(username);
}
throw new UsernameAlreadyInUseException(username);
} catch (final EntityNotFoundException e) {
// Ok - not exists yet
}
}
final String email = StringUtils.trimToNull(element.getEmail());
if (settingsService.getLocalSettings().isEmailUnique() && StringUtils.isNotEmpty(email)) {
if (!uniqueObjectHandler.tryAcquire(Pair.<Object, Object> of(email, email))) {
throw new ValidationException(new UniqueError(email));
}
}
final User user = element.getUser();
// Create a salt value
if (user.getSalt() == null) {
user.setSalt(hashHandler.newSalt());
}
// If a password exists, ensure it's hashed
if (StringUtils.isNotEmpty(user.getPassword())) {
// When the registration is not pre-hashed, hash the password
if (!when.isPreHashed()) {
user.setPassword(hashHandler.hash(user.getSalt(), user.getPassword()));
}
// When not forcing to change (passwordDate == null), set a password date
if (!forceChangePassword) {
user.setPasswordDate(Calendar.getInstance());
}
}
// If a pin exists, ensure it's hashed
if (user instanceof MemberUser) {
final MemberUser memberUser = (MemberUser) user;
if (StringUtils.isNotEmpty(memberUser.getPin()) && !when.isPreHashed()) {
memberUser.setPin(hashHandler.hash(user.getSalt(), memberUser.getPin()));
}
}
// Insert
Calendar creationDate = element.getCreationDate();
if (creationDate == null) {
creationDate = Calendar.getInstance();
element.setCreationDate(creationDate);
}
element = elementDao.insert(element);
if (element instanceof Member) {
final Member member = (Member) element;
// Handle the member accounts
handleAccounts(member);
// When the member has been activated, send the activation e-mail
if (member.isActive()) {
sendActivationMailIfNeeded(activationMail, member);
}
// Fetch member group
final MemberGroup group = fetchService.fetch(member.getMemberGroup(), MemberGroup.Relationships.CHANNELS, MemberGroup.Relationships.DEFAULT_MAIL_MESSAGES, MemberGroup.Relationships.SMS_MESSAGES, MemberGroup.Relationships.DEFAULT_SMS_MESSAGES);
// Copy default channels access from to member
final Collection<Channel> memberChannels = new ArrayList<Channel>(group.getDefaultChannels());
member.setChannels(memberChannels);
element = (E) elementDao.update(member, false);
// Insert the default notification preferences
final List<NotificationPreference> preferences = new ArrayList<NotificationPreference>();
final Collection<Type> defaultMailMessages = group.getDefaultMailMessages();
final Collection<Type> smsMessages = group.getSmsMessages();
final Collection<Type> defaultSmsMessages = group.getDefaultSmsMessages();
for (final Type type : Type.values()) {
final NotificationPreference preference = new NotificationPreference();
preference.setEmail(defaultMailMessages.contains(type));
preference.setMessage(true);
if (smsMessages.contains(type) && defaultSmsMessages.contains(type)) {
preference.setSms(true);
}
preference.setMember(member);
preference.setType(type);
preferences.add(preference);
}
preferenceService.save(member, preferences);
// Create the brokering when there is a broker set
final Member broker = member.getBroker();
if (broker != null) {
brokeringService.create(broker, member);
}
}
// Create initial group history log
createGroupHistoryLog(element, element.getGroup(), creationDate);
} else {
// Some properties cannot be saved using this method. Load the db state
final Element saved = elementDao.load(element.getId());
element.setCreationDate(saved.getCreationDate());
element.setGroup(saved.getGroup());
final User user = saved.getUser();
if (element instanceof Member) {
/*
* At this point if there is not a valid user, the update was invoked through an unrestricted web service client.
*/
final boolean isWebServiceInvocation = !LoggedUser.hasUser();
final Member member = (Member) element;
// Check if the name has changed
final String savedName = saved.getName();
final String givenName = element.getName();
if (!savedName.equals(givenName)) {
final boolean canChangeName =
isWebServiceInvocation ||
permissionService.permission(member)
.admin(AdminMemberPermission.MEMBERS_CHANGE_NAME)
.broker(BrokerPermission.MEMBERS_CHANGE_NAME)
.member(MemberPermission.PROFILE_CHANGE_NAME)
.hasPermission();
if (!canChangeName) {
// No permissions. Ensure the name is not changed
member.setName(savedName);
}
}
// Check if the email has changed
final String savedEmail = StringUtils.trimToNull(saved.getEmail());
final String givenEmail = StringUtils.trimToNull(element.getEmail());
if (!ObjectUtils.equals(savedEmail, givenEmail)) {
final boolean canChangeEmail =
isWebServiceInvocation ||
permissionService.permission(member)
.admin(AdminMemberPermission.MEMBERS_CHANGE_EMAIL)
.broker(BrokerPermission.MEMBERS_CHANGE_EMAIL)
.member(MemberPermission.PROFILE_CHANGE_EMAIL)
.hasPermission();
if (!canChangeEmail) {
// No permissions. Ensure the email is not changed
member.setEmail(savedEmail);
} else {
pendingEmailChangeDao.removeAll(member);
// Check if there is e-mail validation for changing e-mail
if (givenEmail != null) {
nl.strohalm.cyclos.entities.groups.MemberGroupSettings.EmailValidation emailValidation = getEmailValidation(when, element);
if (member.getMemberGroup().getMemberSettings().getEmailValidation().contains(emailValidation)) {
// E-mail validation is enabled. Keep the same saved e-mail and create a pending e-mail change
member.setEmail(savedEmail);
PendingEmailChange pec = new PendingEmailChange();
pec.setBy(LoggedUser.element());
pec.setCreationDate(Calendar.getInstance());
pec.setMember(member);
pec.setNewEmail(givenEmail);
pec.setRemoteAddress(LoggedUser.remoteAddress());
pec.setValidationKey(RandomStringUtils.randomAlphanumeric(64));
pec = pendingEmailChangeDao.insert(pec);
resendEmail(pec);
}
}
}
}
// Check if the username has changed
final String savedUsername = saved.getUsername();
final String givenUsername = element.getUsername();
boolean canChangeUsername;
if (settingsService.getAccessSettings().getUsernameGeneration() == UsernameGeneration.NONE) {
canChangeUsername = isWebServiceInvocation ||
permissionService.permission(member)
.admin(AdminMemberPermission.MEMBERS_CHANGE_USERNAME)
.broker(BrokerPermission.MEMBERS_CHANGE_USERNAME)
.member(MemberPermission.PROFILE_CHANGE_USERNAME)
.hasPermission();
} else {
// Even with permissions, when username is generated it cannot be changed
canChangeUsername = false;
}
if (!savedUsername.equals(givenUsername) && canChangeUsername) {
// Log the change
final UsernameChangeLog log = new UsernameChangeLog();
log.setDate(Calendar.getInstance());
log.setBy(LoggedUser.element());
log.setUser(user);
log.setPreviousUsername(savedUsername);
log.setNewUsername(givenUsername);
usernameChangeLogDao.insert(log);
// Save the username
user.setUsername(givenUsername);
// Set the owner name on each account
final List<? extends Account> accounts = accountService.getAccounts(member);
for (final Account account : accounts) {
account.setOwnerName(givenUsername);
}
}
} else if (element instanceof Operator) {
if (LoggedUser.isMember()) {
// Save the username: a member always can change the operator's username
user.setUsername(element.getUsername());
}
}
element.setUser(user);
if (element instanceof Member) {
final Member member = (Member) element;
final Member savedMember = (Member) saved;
member.setActivationDate(savedMember.getActivationDate());
member.setBroker(savedMember.getBroker());
member.setChannels(accessService.getChannelsEnabledForMember(savedMember));
} else if (element instanceof Operator) {
final Operator operator = (Operator) element;
final Operator savedOperator = (Operator) saved;
operator.setMember(savedOperator.getMember());
if (!LoggedUser.isMember()) { // preserve the saved name: only the member can change the operator's name
operator.setName(savedOperator.getName());
}
}
// Update
element = elementDao.update(element);
}
// Save the custom fields
PropertyHelper.set(element, "customValues", values);
if (element instanceof Member) {
memberCustomFieldService.saveValues((Member) element);
} else if (element instanceof Administrator) {
adminCustomFieldService.saveValues((Administrator) element);
} else if (element instanceof Operator) {
operatorCustomFieldService.saveValues((Operator) element);
}
// Reindex the element
elementDao.addToIndex(element);
return element;
}
private void sendActivationMailIfNeeded(final ActivationMail activationMail, final Member member) {
if (activationMail == ActivationMail.IGNORE || StringUtils.isEmpty(member.getEmail())) {
return;
}
final MemberGroup group = member.getMemberGroup();
final User user = member.getUser();
// Check if the member is activated, and activation mail can be sent
final boolean sendPasswordByEmail = group.getMemberSettings().isSendPasswordByEmail();
String password = null;
if (sendPasswordByEmail && StringUtils.isEmpty(user.getPassword())) {
// Generate a password
password = accessService.generatePassword(group);
user.setPassword(hashHandler.hash(user.getSalt(), password));
member.setUser(userDao.update(user));
member.getMemberUser().setPasswordGenerated(true);
}
// Send activation mail
mailHandler.sendActivation(activationMail == ActivationMail.THREADED, member, password);
}
private String toString(final Collection<Channel> channels) {
StringBuilder str = new StringBuilder();
for (Channel channel : channels) {
channel = channelService.load(channel.getId());
if (str.length() > 0) {
str.append(", ");
}
str.append(channel.getDisplayName());
}
return str.toString();
}
/**
* Updates end date of last group history log and create new group history log
*/
private void updateGroupHistoryLogs(final Element element, final Group newGroup, final Calendar date) {
// Update end date of last group history log
final GroupHistoryLog lastGhl = groupHistoryLogDao.getLastGroupHistoryLog(element);
if (lastGhl != null) {
lastGhl.getPeriod().setEnd(date);
groupHistoryLogDao.update(lastGhl);
}
// Create new group history log
createGroupHistoryLog(element, newGroup, date);
}
}