package de.rwth.idsg.bikeman.repository;
import de.rwth.idsg.bikeman.domain.*;
import de.rwth.idsg.bikeman.domain.Address_;
import de.rwth.idsg.bikeman.domain.BookedTariff_;
import de.rwth.idsg.bikeman.domain.CardAccount_;
import de.rwth.idsg.bikeman.domain.Customer_;
import de.rwth.idsg.bikeman.domain.Tariff_;
import de.rwth.idsg.bikeman.security.AuthoritiesConstants;
import de.rwth.idsg.bikeman.web.rest.dto.modify.CreateEditAddressDTO;
import de.rwth.idsg.bikeman.web.rest.dto.modify.CreateEditCustomerDTO;
import de.rwth.idsg.bikeman.web.rest.dto.view.ViewCustomerDTO;
import de.rwth.idsg.bikeman.web.rest.exception.DatabaseException;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.LocalDateTime;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import javax.inject.Inject;
import javax.persistence.EntityExistsException;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.*;
import java.util.HashSet;
import java.util.List;
/**
* Created by sgokay on 05.06.14.
*/
@Repository
@Slf4j
public class CustomerRepositoryImpl implements CustomerRepository {
@Inject private PasswordEncoder passwordEncoder;
@Inject private TariffRepository tariffRepository;
private enum Operation {CREATE, UPDATE}
private enum FindType {ALL, BY_NAME, BY_LOGIN}
@PersistenceContext
private EntityManager em;
@Override
@Transactional(readOnly = true)
public List<ViewCustomerDTO> findAll() throws DatabaseException {
try {
CriteriaBuilder builder = em.getCriteriaBuilder();
return em.createQuery(
getQuery(builder, FindType.ALL, null, null)
).getResultList();
} catch (Exception e) {
throw new DatabaseException("Failed during database operation.", e);
}
}
@Override
@Transactional(readOnly = true)
public List<ViewCustomerDTO> findbyName(String name) throws DatabaseException {
List<ViewCustomerDTO> list;
try {
CriteriaBuilder builder = em.getCriteriaBuilder();
list = em.createQuery(
getQuery(builder, FindType.BY_NAME, name, null)
).getResultList();
} catch (Exception e) {
throw new DatabaseException("Failed during database operation.", e);
}
if (list.isEmpty()) {
throw new DatabaseException("No customer found with name " + name);
} else {
return list;
}
}
@Override
@Transactional(readOnly = true)
public ViewCustomerDTO findbyLogin(String login) throws DatabaseException {
try {
CriteriaBuilder builder = em.getCriteriaBuilder();
return em.createQuery(
getQuery(builder, FindType.BY_LOGIN, null, login)
).getSingleResult();
} catch (NoResultException e) {
throw new DatabaseException("No customer found with login " + login, e);
} catch (Exception e) {
throw new DatabaseException("Failed during database operation.", e);
}
}
@Override
@Transactional(readOnly = true)
public Customer findOne(long userId) throws DatabaseException {
return getCustomerEntity(userId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void activate(long userId) throws DatabaseException {
Customer customer = getCustomerEntity(userId);
try {
customer.setIsActivated(true);
customer.getCardAccount().setAuthenticationTrialCount(0);
customer.getCardAccount().setOperationState(OperationState.OPERATIVE);
em.merge(customer);
log.debug("Activated customer {}", customer);
} catch (Exception e) {
throw new DatabaseException("Failed to activate customer with userId " + userId, e);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deactivate(long userId) throws DatabaseException {
Customer customer = getCustomerEntity(userId);
try {
customer.setIsActivated(false);
customer.getCardAccount().setOperationState(OperationState.INOPERATIVE);
em.merge(customer);
log.debug("Deactivated customer {}", customer);
} catch (Exception e) {
throw new DatabaseException("Failed to deactivate customer with userId " + userId, e);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void create(CreateEditCustomerDTO dto) throws DatabaseException {
Customer customer = new Customer();
setFields(customer, dto, Operation.CREATE);
try {
em.persist(customer);
log.debug("Created new customer {}", customer);
} catch (EntityExistsException e) {
throw new DatabaseException("This customer exists already.", e);
} catch (Exception e) {
throw new DatabaseException("Failed to create a new customer.", e);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void update(CreateEditCustomerDTO dto) throws DatabaseException {
final Long userId = dto.getUserId();
if (userId == null) {
return;
}
Customer customer = getCustomerEntity(userId);
try {
setFields(customer, dto, Operation.UPDATE);
em.merge(customer);
log.debug("Updated customer {}", customer);
} catch (Exception e) {
throw new DatabaseException("Failed to update customer with userId " + userId, e);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void delete(long userId) throws DatabaseException {
Customer customer = getCustomerEntity(userId);
try {
em.remove(customer);
log.debug("Deleted customer {}", customer);
} catch (Exception e) {
throw new DatabaseException("Failed to delete customer with userId " + userId, e);
}
}
/**
* Returns a customer, or throws exception when no customer exists.
*/
@Transactional(readOnly = true)
private Customer getCustomerEntity(long userId) throws DatabaseException {
Customer customer = em.find(Customer.class, userId);
if (customer == null) {
throw new DatabaseException("No customer with userId " + userId);
} else {
return customer;
}
}
/**
* This method sets the fields of the customer to the values in DTO.
*/
private void setFields(Customer customer, CreateEditCustomerDTO dto, Operation operation) {
// TODO: Should the login be changeable? Who sets the field? Clarify!
customer.setLogin(dto.getLogin());
customer.setCustomerId(dto.getCustomerId());
customer.setFirstname(dto.getFirstname());
customer.setLastname(dto.getLastname());
customer.setBirthday(dto.getBirthday());
customer.setIsActivated(dto.getIsActivated());
if (dto.getPassword() != null) {
customer.setPassword(passwordEncoder.encode(dto.getPassword()));
}
switch (operation) {
case CREATE:
// for create (brand new address entity)
Address newAdd = new Address();
CreateEditAddressDTO newDtoAdd = dto.getAddress();
newAdd.setStreetAndHousenumber(newDtoAdd.getStreetAndHousenumber());
newAdd.setZip(newDtoAdd.getZip());
newAdd.setCity(newDtoAdd.getCity());
newAdd.setCountry(newDtoAdd.getCountry());
customer.setAddress(newAdd);
CardAccount newCardAccount = new CardAccount();
newCardAccount.setOwnerType(CustomerType.CUSTOMER);
newCardAccount.setCardId(dto.getCardId());
newCardAccount.setCardPin(dto.getCardPin());
newCardAccount.setUser(customer);
newCardAccount.setOperationState(OperationState.OPERATIVE);
customer.setCardAccount(newCardAccount);
BookedTariff newBookedTariff = new BookedTariff();
newBookedTariff.setBookedFrom(new LocalDateTime());
if (tariffRepository.findByName(dto.getTariff()).getTerm() == null) {
newBookedTariff.setBookedUntil(null);
} else {
newBookedTariff.setBookedUntil(new LocalDateTime().plusDays(
tariffRepository.findByName(dto.getTariff()).getTerm()
));
}
newBookedTariff.setTariff(tariffRepository.findByName(dto.getTariff()));
newBookedTariff.setUsedCardAccount(newCardAccount);
newCardAccount.setCurrentTariff(newBookedTariff);
HashSet<Authority> authorities = new HashSet<>();
authorities.add(new Authority(AuthoritiesConstants.CUSTOMER));
customer.setAuthorities(authorities);
break;
case UPDATE:
// for edit (keep the address ID)
Address add = customer.getAddress();
CreateEditAddressDTO dtoAdd = dto.getAddress();
add.setStreetAndHousenumber(dtoAdd.getStreetAndHousenumber());
add.setZip(dtoAdd.getZip());
add.setCity(dtoAdd.getCity());
add.setCountry(dtoAdd.getCountry());
CardAccount cardAccount = customer.getCardAccount();
cardAccount.setOwnerType(CustomerType.CUSTOMER);
cardAccount.setCardId(dto.getCardId());
cardAccount.setCardPin(dto.getCardPin());
boolean newCardIsOperative = OperationState.OPERATIVE.equals(dto.getCardOperationState());
boolean oldCardIsInoperative = OperationState.INOPERATIVE.equals(cardAccount.getOperationState());
if (newCardIsOperative && oldCardIsInoperative) {
cardAccount.setAuthenticationTrialCount(0);
}
if (dto.getIsActivated() || !newCardIsOperative) {
cardAccount.setOperationState(dto.getCardOperationState());
}
// don't update the tariff if it has not been changed
if (!dto.getTariff().equals(cardAccount.getCurrentTariff().getName() )) {
BookedTariff updateBookedTariff = new BookedTariff();
updateBookedTariff.setBookedFrom(new LocalDateTime());
if (tariffRepository.findByName(dto.getTariff()).getTerm() == null) {
updateBookedTariff.setBookedUntil(null);
} else {
updateBookedTariff.setBookedUntil(new LocalDateTime().plusDays(
tariffRepository.findByName(dto.getTariff()).getTerm()
));
}
updateBookedTariff.setTariff(tariffRepository.findByName(dto.getTariff()));
cardAccount.setCurrentTariff(updateBookedTariff);
}
break;
}
}
/**
* This method returns the query to get information of customers for various lookup cases
*/
private CriteriaQuery<ViewCustomerDTO> getQuery(CriteriaBuilder builder, FindType findType,
String name,
String login) {
CriteriaQuery<ViewCustomerDTO> criteria = builder.createQuery(ViewCustomerDTO.class);
Root<Customer> customer = criteria.from(Customer.class);
Join<Customer, Address> address = customer.join(Customer_.address, JoinType.LEFT);
Join<Customer, CardAccount> cardAccount = customer.join(Customer_.cardAccount, JoinType.LEFT);
Join<CardAccount, BookedTariff> bookedTariff = cardAccount.join(CardAccount_.currentTariff, JoinType.LEFT);
criteria.select(
builder.construct(
ViewCustomerDTO.class,
customer.get(Customer_.userId),
customer.get(Customer_.login),
customer.get(Customer_.customerId),
customer.get(Customer_.firstname),
customer.get(Customer_.lastname),
customer.get(Customer_.isActivated),
customer.get(Customer_.birthday),
cardAccount.get(CardAccount_.cardId),
cardAccount.get(CardAccount_.cardPin),
cardAccount.get(CardAccount_.operationState),
bookedTariff.get(BookedTariff_.tariff).get(Tariff_.name),
address.get(Address_.streetAndHousenumber),
address.get(Address_.zip),
address.get(Address_.city),
address.get(Address_.country)
)
);
switch (findType) {
case ALL:
break;
// Case insensitive search
case BY_NAME:
Path<String> firstPath = customer.get(Customer_.firstname);
Expression<String> firstLower = builder.lower(firstPath);
Path<String> lastPath = customer.get(Customer_.lastname);
Expression<String> lastLower = builder.lower(lastPath);
/*
* Frontend can send the search parameter with '+' sign between first and last name (or any substring of them).
* We replace this with '%' since Postgre uses it to match any string of zero or more characters.
*
* The method returns a reference to the old object, when '+' does not occur in the string.
* Therefore, no if-statement is needed.
*/
name = name.replace("+", "%");
criteria.where(
builder.like(
// Concatenate the two columns and search within the resulting representation
// for flexibility, since the user can search by first or last name, or both.
builder.concat(firstLower, lastLower),
// Find a matching sequence anywhere within the concatenated representation
("%" + name + "%").toLowerCase()
)
);
break;
// Case insensitive search
case BY_LOGIN:
Path<String> loginPath = customer.get(Customer_.login);
Expression<String> loginLower = builder.lower(loginPath);
criteria.where(
builder.equal(loginLower, login.toLowerCase())
);
break;
}
return criteria;
}
}