/*
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.services;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import nl.strohalm.cyclos.dao.services.ServiceClientDAO;
import nl.strohalm.cyclos.entities.Relationship;
import nl.strohalm.cyclos.entities.access.Channel;
import nl.strohalm.cyclos.entities.accounts.AccountType;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferType;
import nl.strohalm.cyclos.entities.accounts.transactions.TransferTypeQuery;
import nl.strohalm.cyclos.entities.exceptions.EntityNotFoundException;
import nl.strohalm.cyclos.entities.members.Element;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.entities.services.ServiceClient;
import nl.strohalm.cyclos.entities.services.ServiceClientQuery;
import nl.strohalm.cyclos.entities.services.ServiceOperation;
import nl.strohalm.cyclos.services.access.ChannelServiceLocal;
import nl.strohalm.cyclos.services.fetch.FetchServiceLocal;
import nl.strohalm.cyclos.services.transactions.TransactionContext;
import nl.strohalm.cyclos.services.transfertypes.TransferTypeServiceLocal;
import nl.strohalm.cyclos.utils.DataIteratorHelper;
import nl.strohalm.cyclos.utils.InternetAddressHelper;
import nl.strohalm.cyclos.utils.InternetAddressHelper.AddressType;
import nl.strohalm.cyclos.utils.RelationshipHelper;
import nl.strohalm.cyclos.utils.query.QueryParameters.ResultType;
import nl.strohalm.cyclos.utils.validation.GeneralValidation;
import nl.strohalm.cyclos.utils.validation.InvalidError;
import nl.strohalm.cyclos.utils.validation.PropertyValidation;
import nl.strohalm.cyclos.utils.validation.ValidationError;
import nl.strohalm.cyclos.utils.validation.Validator;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
/**
* Implementation for ServiceClientService
* @author luis
*/
public class ServiceClientServiceImpl implements ServiceClientServiceLocal {
/**
* Validates a hostname so that it may be a qualified host name, an IP address or a IP address range in form A.B.C.D-E
* @author luis
*/
private class HostnameValidation implements PropertyValidation {
private static final long serialVersionUID = 8330071155849944811L;
@Override
public ValidationError validate(final Object object, final Object property, final Object value) {
if (value == null || "".equals(value)) {
return null;
}
final String address = value.toString();
final AddressType addressType = InternetAddressHelper.resolveAddressType(address);
if (addressType == null) {
return new InvalidError();
}
if (!resolveAddress((ServiceClient) object)) {
return new InvalidError();
}
return null;
}
};
private static class TransferTypeNameComparator implements Comparator<TransferType> {
@Override
public int compare(final TransferType tt1, final TransferType tt2) {
return tt1.getName().compareTo(tt2.getName());
}
}
private static Comparator<TransferType> TT_COMPARATOR = new TransferTypeNameComparator();
private static List<String> FORBIDDEN_CHANNELS = Arrays.asList(Channel.WEB, Channel.WAP1, Channel.WAP2, Channel.POSWEB, Channel.POS);
private ServiceClientDAO serviceClientDao;
private TransferTypeServiceLocal transferTypeService;
private ChannelServiceLocal channelService;
private FetchServiceLocal fetchService;
@Override
public int delete(final Long... ids) {
return serviceClientDao.delete(ids);
}
@Override
public ServiceClient findByAddressAndCredentials(final String address, final String username, final String password) {
final ServiceClientQuery query = new ServiceClientQuery();
query.fetch(RelationshipHelper.nested(ServiceClient.Relationships.MEMBER, Element.Relationships.GROUP));
query.setUniqueResult();
query.setAddress(address);
query.setUsername(username);
query.setPassword(password);
final List<ServiceClient> list = search(query);
if (list.isEmpty()) {
throw new EntityNotFoundException(ServiceClient.class);
}
return list.iterator().next();
}
@Override
public List<Channel> listPossibleChannels() {
final List<Channel> channels = new ArrayList<Channel>(channelService.list());
for (final Iterator<Channel> iterator = channels.iterator(); iterator.hasNext();) {
final Channel channel = iterator.next();
if (FORBIDDEN_CHANNELS.contains(channel.getInternalName())) {
iterator.remove();
}
}
return channels;
}
@Override
public List<TransferType> listPossibleDoPaymentTypes(final ServiceClient client) {
final Channel channel = client.getChannel();
if (channel == null) {
return Collections.emptyList();
}
final TransferTypeQuery query = new TransferTypeQuery();
query.setContext(TransactionContext.PAYMENT);
query.setChannel(channel.getInternalName());
Member member = client.getMember();
if (member != null) {
member = fetchService.fetch(member, Element.Relationships.GROUP);
query.setFromOwner(member);
}
final List<TransferType> tTypes = transferTypeService.search(query);
// the System to System payments are a kind of self payment transfers
if (member == null) {
query.setContext(TransactionContext.SELF_PAYMENT);
tTypes.addAll(transferTypeService.search(query));
}
Collections.sort(tTypes, TT_COMPARATOR);
return tTypes;
}
@Override
public List<TransferType> listPossibleReceivePaymentTypes(final ServiceClient client) {
Member member = client.getMember();
final Channel channel = client.getChannel();
if (member == null || channel == null) {
return Collections.emptyList();
}
final TransferTypeQuery query = new TransferTypeQuery();
query.setContext(TransactionContext.PAYMENT);
query.setChannel(channel.getInternalName());
member = fetchService.fetch(member, Element.Relationships.GROUP);
query.setToOwner(member);
query.setFromNature(AccountType.Nature.MEMBER);
return transferTypeService.search(query);
}
@Override
public ServiceClient load(final Long id, final Relationship... fetch) {
return serviceClientDao.load(id, fetch);
}
@Override
public void resolveAllIpAddresses() {
final ServiceClientQuery query = new ServiceClientQuery();
query.setResultType(ResultType.ITERATOR);
final List<ServiceClient> clients = serviceClientDao.search(query);
try {
for (final ServiceClient client : clients) {
try {
if (resolveAddress(client)) {
serviceClientDao.update(client);
}
} catch (final Exception e) {
// Ignore
}
}
} finally {
DataIteratorHelper.close(clients);
}
}
@Override
public ServiceClient save(final ServiceClient client) {
validate(client);
final Channel channel = fetchService.fetch(client.getChannel());
final boolean isWebShop = channel != null && Channel.WEBSHOP.equals(channel.getInternalName());
// Enforce collections
if (isWebShop || channel == null) {
// Only clients with channel != WEBSHOP may do / receive payments
client.setDoPaymentTypes(null);
client.setReceivePaymentTypes(null);
client.setChargebackPaymentTypes(null);
} else if (client.getMember() == null) {
// Clients without members cannot receive payments
client.setReceivePaymentTypes(null);
}
// Enforce the permissions based on collections
Set<ServiceOperation> permissions = client.getPermissions();
if (permissions == null) {
permissions = new HashSet<ServiceOperation>();
client.setPermissions(permissions);
} else {
// For webshops, remove all operations which are not WEBSHOP
if (isWebShop) {
for (final Iterator<ServiceOperation> iterator = permissions.iterator(); iterator.hasNext();) {
final ServiceOperation operation = iterator.next();
if (operation != ServiceOperation.WEBSHOP) {
iterator.remove();
}
}
} else {
// Channels which are not webshop cannot have webshop, search ads or search members permissions
permissions.remove(ServiceOperation.WEBSHOP);
// Empty channel cannot see access information
if (channel == null) {
permissions.remove(ServiceOperation.ACCESS);
}
}
}
if (CollectionUtils.isEmpty(client.getDoPaymentTypes())) {
permissions.remove(ServiceOperation.DO_PAYMENT);
} else {
permissions.add(ServiceOperation.DO_PAYMENT);
}
if (client.getMember() == null || CollectionUtils.isEmpty(client.getReceivePaymentTypes())) {
permissions.remove(ServiceOperation.RECEIVE_PAYMENT);
} else {
permissions.add(ServiceOperation.RECEIVE_PAYMENT);
}
if (CollectionUtils.isEmpty(client.getChargebackPaymentTypes())) {
permissions.remove(ServiceOperation.CHARGEBACK);
} else {
permissions.add(ServiceOperation.CHARGEBACK);
}
if (client.getMember() != null || client.getChannel() == null || CollectionUtils.isEmpty(client.getManageGroups())) {
client.setManageGroups(null);
permissions.remove(ServiceOperation.MANAGE_MEMBERS);
} else {
permissions.add(ServiceOperation.MANAGE_MEMBERS);
}
// Save
if (client.isTransient()) {
return serviceClientDao.insert(client);
} else {
return serviceClientDao.update(client);
}
}
@Override
public List<ServiceClient> search(final ServiceClientQuery query) {
return serviceClientDao.search(query);
}
public void setChannelServiceLocal(final ChannelServiceLocal channelService) {
this.channelService = channelService;
}
public void setFetchServiceLocal(final FetchServiceLocal fetchService) {
this.fetchService = fetchService;
}
public void setServiceClientDao(final ServiceClientDAO serviceClientDao) {
this.serviceClientDao = serviceClientDao;
}
public void setTransferTypeServiceLocal(final TransferTypeServiceLocal transferTypeService) {
this.transferTypeService = transferTypeService;
}
@Override
public void validate(final ServiceClient client) {
getValidator(client).validate(client);
}
private Validator getValidator(final ServiceClient client) {
final Validator validator = new Validator("serviceClient");
validator.property("name").required().maxLength(100);
validator.property("hostname").key("serviceClient.address").required().add(new HostnameValidation()).maxLength(100);
validator.property("username").maxLength(100);
validator.property("password").maxLength(100);
validator.property("channel").anyOf(listPossibleChannels().toArray());
final Channel channel = fetchService.fetch(client.getChannel());
if (channel != null && !Channel.WEBSHOP.equals(channel.getInternalName())) {
final Object[] doPaymentTTs = listPossibleDoPaymentTypes(client).toArray();
final Object[] receiveTTs = listPossibleReceivePaymentTypes(client).toArray();
validator.property("doPaymentTypes").key("serviceOperation.DO_PAYMENT").anyOf(doPaymentTTs);
validator.property("receivePaymentTypes").key("serviceOperation.RECEIVE_PAYMENT").anyOf(receiveTTs);
validator.property("receivePaymentTypes").key("serviceOperation.CHARGEBACK").anyOf(client.getMember() == null ? doPaymentTTs : receiveTTs);
}
validator.general(new GeneralValidation() {
private static final long serialVersionUID = -6137330080312890785L;
@Override
public ValidationError validate(final Object object) {
final ServiceClient client = (ServiceClient) object;
final String username = StringUtils.trimToNull(client.getUsername());
final String password = StringUtils.trimToNull(client.getPassword());
int nonEmpty = 0;
if (username != null) {
nonEmpty++;
}
if (password != null) {
nonEmpty++;
}
if (nonEmpty == 1) {
return new ValidationError("serviceClient.error.empty.usernameOrPassword");
}
return null;
}
});
return validator;
}
private boolean resolveAddress(final ServiceClient client) {
final String hostname = client.getHostname();
final AddressType addressType = InternetAddressHelper.resolveAddressType(hostname);
if (addressType == null) {
// Invalid ?!?
return false;
}
final String addressBegin = client.getAddressBegin();
final String addressEnd = client.getAddressBegin();
switch (addressType) {
case HOSTNAME:
try {
// Resolve the ip address
final InetAddress[] addr = InetAddress.getAllByName(hostname);
final String ip = InternetAddressHelper.padAddress(addr[0].getHostAddress());
client.setAddressBegin(ip);
client.setAddressEnd(ip);
} catch (final UnknownHostException e) {
// Error resolving ip address. Leave the previous one
}
break;
case SIMPLE_IP:
// It's a fixed ip
final String paddedAddress = InternetAddressHelper.padAddress(hostname);
client.setAddressBegin(paddedAddress);
client.setAddressEnd(paddedAddress);
break;
case IP_RANGE:
// An ip range
final String[] range = InternetAddressHelper.getRangeBoundaries(hostname);
client.setAddressBegin(InternetAddressHelper.padAddress(range[0]));
client.setAddressEnd(InternetAddressHelper.padAddress(range[1]));
break;
}
return !ObjectUtils.equals(client.getAddressBegin(), addressBegin) || !ObjectUtils.equals(client.getAddressEnd(), addressEnd);
}
}