/*
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.accounts.cards;
import java.math.BigInteger;
import java.util.Calendar;
import java.util.List;
import nl.strohalm.cyclos.dao.accounts.cards.CardDAO;
import nl.strohalm.cyclos.dao.accounts.cards.CardLogDAO;
import nl.strohalm.cyclos.entities.Relationship;
import nl.strohalm.cyclos.entities.accounts.cards.Card;
import nl.strohalm.cyclos.entities.accounts.cards.Card.Status;
import nl.strohalm.cyclos.entities.accounts.cards.CardLog;
import nl.strohalm.cyclos.entities.accounts.cards.CardQuery;
import nl.strohalm.cyclos.entities.accounts.cards.CardType;
import nl.strohalm.cyclos.entities.accounts.cards.CardType.CardSecurityCode;
import nl.strohalm.cyclos.entities.groups.BasicGroupSettings.PasswordPolicy;
import nl.strohalm.cyclos.entities.members.Element;
import nl.strohalm.cyclos.entities.members.FullTextMemberQuery;
import nl.strohalm.cyclos.entities.members.Member;
import nl.strohalm.cyclos.exceptions.PermissionDeniedException;
import nl.strohalm.cyclos.services.InitializingService;
import nl.strohalm.cyclos.services.access.AccessServiceLocal;
import nl.strohalm.cyclos.services.elements.BulkMemberActionResultVO;
import nl.strohalm.cyclos.services.elements.ElementServiceLocal;
import nl.strohalm.cyclos.services.fetch.FetchServiceLocal;
import nl.strohalm.cyclos.utils.CacheCleaner;
import nl.strohalm.cyclos.utils.DateHelper;
import nl.strohalm.cyclos.utils.HashHandler;
import nl.strohalm.cyclos.utils.RangeConstraint;
import nl.strohalm.cyclos.utils.RelationshipHelper;
import nl.strohalm.cyclos.utils.access.LoggedUser;
import nl.strohalm.cyclos.utils.validation.LengthValidation;
import nl.strohalm.cyclos.utils.validation.ValidationError;
import nl.strohalm.cyclos.utils.validation.ValidationException;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.math.RandomUtils;
/**
* Card Service Implementation
* @author rodrigo
*/
public class CardServiceImpl implements CardServiceLocal, InitializingService {
private FetchServiceLocal fetchService;
private CardDAO cardDao;
private CardLogDAO cardLogDao;
private ElementServiceLocal elementService;
private AccessServiceLocal accessService;
private static final char NUMERIC_CONSTANT = '#';
private HashHandler hashHandler;
@Override
public Card activateCard(Card card, String cardCode) {
if (card != null && card.getId() > 0) {
final List<Card> activeCards = cardDao.searchActiveCards(card.getOwner().getId(), card.getId());
if (activeCards != null) {
for (final Card activeCard : activeCards) {
cancelCard(activeCard);
}
}
card = fetchService.fetch(card, Card.Relationships.CARD_TYPE, RelationshipHelper.nested(Card.Relationships.OWNER, Element.Relationships.USER));
if (card.getCardType().getCardSecurityCode() == CardType.CardSecurityCode.MANUAL) {
validateCardSecurityCode(card, cardCode);
if (!card.getCardType().isShowCardSecurityCode()) {
cardCode = hashHandler.hash(card.getOwner().getUser().getSalt(), cardCode);
}
card.setCardSecurityCode(cardCode);
}
card.setStatus(Card.Status.ACTIVE);
card.setActivationDate(Calendar.getInstance());
cardDao.update(card, true);
generateLog(card);
}
return card;
}
@Override
public Card blockCard(final Card card) {
if (card != null && card.getId() > 0) {
card.setStatus(Card.Status.BLOCKED);
cardDao.update(card, true);
generateLog(card);
}
return card;
}
@Override
@SuppressWarnings("unchecked")
public BulkMemberActionResultVO bulkGenerateNewCard(final FullTextMemberQuery query, final boolean generateForPending, final boolean generateForActive) {
int changed = 0;
int unchanged = 0;
boolean generateNewCard = true;
// force the result type to ITERATOR to avoid load all members in memory
query.setIterateAll();
final List<Member> members = (List<Member>) elementService.fullTextSearch(query);
final CacheCleaner cacheCleaner = new CacheCleaner(fetchService);
for (Member member : members) {
member = fetchService.fetch(member, Element.Relationships.GROUP);
if (member.getMemberGroup().getCardType() != null) {
final Card card = cardDao.getLastCard(member.getId());
if (card != null) {
if (card.getStatus().equals(Card.Status.PENDING) && !generateForPending) {
generateNewCard = false;
}
if (card.getStatus().equals(Card.Status.ACTIVE) && !generateForActive) {
generateNewCard = false;
}
}
} else {
generateNewCard = false;
}
if (generateNewCard) {
generateNewCard(member);
changed++;
} else {
unchanged++;
}
generateNewCard = true;
cacheCleaner.clearCache();
}
return new BulkMemberActionResultVO(changed, unchanged);
}
@Override
public void cancelAllMemberCards(final Member member) {
cardDao.cancelAllMemberCards(member);
}
@Override
public Card cancelCard(final Card card) {
if (card != null && card.getId() > 0) {
card.setStatus(Card.Status.CANCELED);
cardDao.update(card, true);
generateLog(card);
}
return card;
}
@Override
public Card changeCardCode(Card card, String code) {
card = fetchService.fetch(card, RelationshipHelper.nested(Card.Relationships.OWNER, Element.Relationships.USER), Card.Relationships.CARD_TYPE);
if (card.getCardType().getCardSecurityCode() != CardSecurityCode.MANUAL) {
throw new PermissionDeniedException();
}
validateCardSecurityCode(card, code);
if (!card.getCardType().isShowCardSecurityCode()) {
code = hashHandler.hash(code, card.getOwner().getUser().getSalt());
}
card.setCardSecurityCode(code);
cardDao.update(card);
return card;
}
@Override
public Card generateNewCard(final Member member) {
final Card lastCard = cardDao.getLastCard(member.getId());
if (lastCard != null && lastCard.getStatus() == Card.Status.PENDING) {
cancelCard(lastCard);
}
final Card newCard = buildNewCard(member);
cardDao.insert(newCard, false);
generateLog(newCard);
return newCard;
}
@Override
public Card getActiveCard(final Member member) {
for (final Card card : getMemberCards(member.getId())) {
if (card.getStatus() == Status.ACTIVE) {
return card;
}
}
return null;
}
public CardDAO getCardDao() {
return cardDao;
}
public CardLogDAO getCardLogDao() {
return cardLogDao;
}
@Override
public void initializeService() {
processCards(Calendar.getInstance());
}
@Override
public Card load(final long cardId, final Relationship... fetch) {
return cardDao.load(cardId, fetch);
}
@Override
public Card loadByNumber(final BigInteger number, final Relationship... fetch) {
return cardDao.loadByNumber(number, fetch);
}
@Override
public List<Card> processCards(final Calendar time) {
return expireCards(time);
}
@Override
public List<Card> search(final CardQuery query) {
return cardDao.search(query);
}
public void setAccessServiceLocal(final AccessServiceLocal accessService) {
this.accessService = accessService;
}
public void setCardDao(final CardDAO cardDao) {
this.cardDao = cardDao;
}
public void setCardLogDao(final CardLogDAO cardLogDao) {
this.cardLogDao = cardLogDao;
}
public void setElementServiceLocal(final ElementServiceLocal elementService) {
this.elementService = elementService;
}
public void setFetchServiceLocal(final FetchServiceLocal fetchService) {
this.fetchService = fetchService;
}
public void setHashHandler(final HashHandler hashHandler) {
this.hashHandler = hashHandler;
}
@Override
public Card unblockCard(final Card card) {
if (card != null && card.getId() > 0) {
card.setStatus(Card.Status.ACTIVE);
cardDao.update(card, true);
generateLog(card);
}
return card;
}
@Override
public void unblockSecurityCode(final Card card) {
accessService.unblockCardSecurityCode(card.getCardNumber());
}
private BigInteger buildCardNumber(final String cardFormatNumber) {
BigInteger generatedNumber;
boolean exists = false;
do {
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < cardFormatNumber.length(); i++) {
final char c = cardFormatNumber.charAt(i);
if (Character.isDigit(c)) {
sb.append(c);
} else if (c == NUMERIC_CONSTANT) {
final int next = i == 0 ? RandomUtils.nextInt(9) + 1 : RandomUtils.nextInt(10); // never generates zero for the first digit
sb.append(next);
}
}
generatedNumber = new BigInteger(sb.toString());
exists = cardDao.existsNumber(generatedNumber);
} while (exists);
return generatedNumber;
}
private String buildCardSecurityCode(final Integer length) {
return RandomStringUtils.randomNumeric(length);
}
private Card buildNewCard(final Member member) {
final Calendar now = Calendar.getInstance();
final Card newCard = new Card();
final CardType cardType = member.getMemberGroup().getCardType();
newCard.setCardType(cardType);
newCard.setOwner(member);
newCard.setCreationDate(now);
newCard.setStatus(Status.PENDING);
final Calendar expirationDate = (Calendar) now.clone();
expirationDate.add(cardType.getDefaultExpiration().getField().getValue(), cardType.getDefaultExpiration().getNumber());
if (cardType.isIgnoreDayInExpirationDate()) {
expirationDate.set(Calendar.DAY_OF_MONTH, expirationDate.getActualMaximum(Calendar.DAY_OF_MONTH));
}
newCard.setExpirationDate(expirationDate);
newCard.setCardNumber(buildCardNumber(cardType.getCardFormatNumber()));
if (cardType.getCardSecurityCode() == CardType.CardSecurityCode.AUTOMATIC) {
newCard.setCardSecurityCode(buildCardSecurityCode(cardType.getCardSecurityCodeLength().getMax()));
}
return newCard;
}
/**
* Verify if has any cards that expires on given time. If has, card status is changed to EXPIRED
* @param time
* @return
*/
private List<Card> expireCards(final Calendar taskTime) {
final List<Card> cards = cardDao.getCardsToExpire(DateHelper.truncate(taskTime));
for (final Card card : cards) {
card.setStatus(Status.EXPIRED);
cardDao.update(card);
generateLog(card);
}
return cards;
}
private void generateLog(final Card card) {
final CardLog cardLog = new CardLog();
if (LoggedUser.hasUser()) {
cardLog.setBy(LoggedUser.element());
}
cardLog.setCard(card);
cardLog.setDate(Calendar.getInstance());
cardLog.setStatus(card.getStatus());
cardLogDao.insert(cardLog);
}
private List<Card> getMemberCards(final long memberId) {
return cardDao.getMemberCards(memberId);
}
private void validateCardSecurityCode(final Card card, final String code) throws ValidationException {
final CardType cardType = card.getCardType();
if (cardType.getCardSecurityCode() != CardSecurityCode.MANUAL) {
throw new ValidationException();
}
final RangeConstraint length = cardType.getCardSecurityCodeLength();
final ValidationError lengthResult = new LengthValidation(length).validate(card, "securityCode", code);
if (lengthResult != null) {
throw new ValidationException("code1", "cardType.cardSecurityCode", lengthResult);
}
final PasswordPolicy passwordPolicy = card.getOwner().getGroup().getBasicSettings().getPasswordPolicy();
final boolean avoidObvious = passwordPolicy != null && passwordPolicy != PasswordPolicy.NONE;
if (avoidObvious && accessService.isObviousCredential(card.getOwner(), code)) {
throw new ValidationException("card.changeSecurityCode.error.obvious");
}
}
}