package org.goat.caprabank.server;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.goat.caprabank.client.BankService;
import org.goat.caprabank.shared.CaprabankException;
import org.goat.caprabank.shared.entity.BankTransaction;
import org.goat.caprabank.shared.entity.User;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.google.gwt.util.tools.shared.Md5Utils;
public class BankServiceImpl extends RemoteServiceServlet implements BankService {
private static final String SESSION_USER_KEY = "CaprabankUser";
private static final Logger log = Logger.getLogger(BankServiceImpl.class.getName());
private static final int USER_LIMIT = 30;
private final DataStore dataStore;
public BankServiceImpl() {
dataStore = new DataStore();
}
//////////////// Public API
@Override
public User login(String username, String password) throws CaprabankException {
validateNotEmpty(username, "username");
validateNotEmpty(password, "password");
User user = dataStore.findUserByUsername(username);
if (user == null || !Arrays.equals(user.passwordHash, hash(password))) {
log.warning("Failed login attempt: " + username + "/" + password + ": " + (user == null ? "user not found." : "incorrect password."));
throw new CaprabankException("Login failed - incorrect username or password.");
}
log.info("Logged in user " + username + ".");
fillDetails(user);
setCurrentUser(user);
return user;
}
@Override
public User register(String firstName, String lastName, String email, String username, String password) throws CaprabankException {
if (dataStore.countUsers() > USER_LIMIT) {
log.warning("Failed registration attempt - user count exceeded.");
throw new CaprabankException("CapraBank is not accepting new customers at the moment.");
}
validateNotEmpty(firstName, "first name");
validateNotEmpty(lastName, "last name");
validateNotEmpty(email, "email");
validateNotEmpty(username, "username");
validateNotEmpty(password, "password");
if (dataStore.findUserByUsername(username) != null) {
log.warning("Failed registration attempt - username already exists: '" + username + ".");
throw new CaprabankException("The username is already in use.");
}
User newUser = new User();
newUser.accountNum = generateId();
newUser.firstName = firstName;
newUser.lastName = lastName;
newUser.email = email;
newUser.username = username;
newUser.passwordHash = hash(password);
newUser.balance = 0;
dataStore.saveUser(newUser);
log.info("Registered user " + username + "(" + (firstName + " " + lastName) + ", " + email + ").");
sendMail(newUser, "Welcome to CapraBank!", "Thank you for registering at CapraBank!<br/><br/>Your account number <b>" + newUser.accountNum + "</b> has been successfully created. Your username is <b>" + newUser.username + "</b>.<br/><br/>Please visit the <a href=\"http://www.caprabank.com\">CapraBank site</a> to keep track of your account.");
setCurrentUser(newUser);
return newUser;
}
@Override
public User getDetails() throws CaprabankException {
return getCurrentUser();
}
@Override
public User transfer(long toAccount, long amount, String comment) throws CaprabankException {
User currentUser = getCurrentUser();
if (toAccount == currentUser.accountNum) {
log.warning("Self-tranfer attempted by user " + currentUser.username + ".");
throw new CaprabankException("Very funny... That's you.");
}
if (amount <= 0) {
log.warning("Negative amount tranfer attempted by user " + currentUser.username + " (amount=" + formatAmount(amount) + ").");
throw new CaprabankException("Very funny...");
}
User toUser = dataStore.findUserByAccountNum(toAccount);
if (toUser == null) {
log.warning("Tranfer to nonexistent account attempted by user " + currentUser.username + " (account=" + toAccount + ").");
throw new CaprabankException("Account " + toAccount + " not found.");
}
if (!"admin".equals(currentUser.username)) {
if (amount > currentUser.balance) {
log.warning("Insufficient funds for tranfer attempted by user " + currentUser.username + " (amount=" + formatAmount(amount) + ", balance=" + formatAmount(currentUser.balance) + ").");
throw new CaprabankException("Insufficient funds.");
}
}
BankTransaction transferTransaction = new BankTransaction();
transferTransaction.transactionId = generateId();
transferTransaction.timestamp = new Date();
transferTransaction.fromAccount = currentUser.accountNum;
transferTransaction.toAccount = toAccount;
transferTransaction.amount = amount;
transferTransaction.comment = comment;
dataStore.saveTransaction(transferTransaction);
log.info("User " + currentUser.username + " transferred " + formatAmount(amount) + " CapraCoins to account " + toAccount + " (" + toUser.username + ") with comment '" + comment + "'.");
String messageBody = "Your CapraBank account " + toUser.accountNum + " just received a transfer of <b>" + formatAmount(amount) + "</b> CapraCoins from account <b>" + currentUser.accountNum + "</b>.";
if (comment != null && !comment.isEmpty()) {
messageBody += "<br/><br/>The sender has added the following comment:<br/><b>\"" + comment + "\"</b>";
}
sendMail(toUser, "Your CapraBank account has received a transfer.", messageBody);
fillDetails(currentUser);
return currentUser;
}
@Override
public void logout() {
boolean loggedOut = false;
HttpServletRequest request = getThreadLocalRequest();
if (request != null) {
HttpSession session = request.getSession();
if (session != null) {
User currentUser = (User) session.getAttribute(SESSION_USER_KEY);
if (currentUser != null) {
loggedOut = true;
session.invalidate();
log.info("Logged out user " + currentUser.username + ".");
}
}
}
if (!loggedOut) {
log.severe("Logout request without an active user.");
}
}
//////////////// Private helpers
private User getCurrentUser() throws CaprabankException {
User currentUser = null;
HttpServletRequest request = getThreadLocalRequest();
if (request != null) {
currentUser = (User) request.getSession(true).getAttribute(SESSION_USER_KEY);
}
if (currentUser == null) {
throw new CaprabankException("User not logged in.");
}
fillDetails(currentUser);
return currentUser;
}
private void setCurrentUser(User user) {
HttpServletRequest request = getThreadLocalRequest();
if (request != null) {
request.getSession(true).setAttribute(SESSION_USER_KEY, user);
}
}
private void fillDetails(User user) {
long balance = 0;
List<BankTransaction> fromAccount = dataStore.findTransactionsFromAccount(user);
for (BankTransaction transaction : fromAccount) {
balance -= transaction.amount;
}
List<BankTransaction> toAccount = dataStore.findTransactionsToAccount(user);
for (BankTransaction transaction : toAccount) {
balance += transaction.amount;
}
user.transactions = new ArrayList<BankTransaction>(fromAccount);
user.transactions.addAll(toAccount);
user.balance = balance;
Collections.sort(user.transactions, new Comparator<BankTransaction>() {
@Override
public int compare(BankTransaction o1, BankTransaction o2) {
return o1.timestamp.compareTo(o2.timestamp);
}
});
}
private void sendMail(User toUser, String subject, String messageBody) {
try {
Session session = Session.getDefaultInstance(new Properties(), null);
Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress("admin@caprabank.com", "CapraBank"));
String fullName = toUser.firstName + " " + toUser.lastName;
msg.addRecipient(Message.RecipientType.TO, new InternetAddress(toUser.email, fullName));
msg.setSubject(subject);
msg.setContent("<html><body>Dear " + fullName + ",<br/><br/>" + messageBody + "<br/><br/>Best regards,<br/>CapraBank.</body></html>", "text/html");
Transport.send(msg);
log.info("Mail sent to user " + toUser.username + " (" + toUser.email + ").");
} catch (Exception e) {
log.log(Level.SEVERE, "Error while sending email to user " + toUser.username + " (" + toUser.email + "): " + e.getMessage(), e);
}
}
//////////////// Utilities
private byte[] hash(String input) {
byte[] result = null;
try {
result = Md5Utils.getMd5Digest(input.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
// Yeah right...
}
return result;
}
private long generateId() {
return (System.currentTimeMillis() - 1326900000000L) / 10L;
}
private String formatAmount(long amount) {
return String.format("%.2f", ((double) amount / 100));
}
private void validateNotEmpty(String value, String name) throws CaprabankException {
if (value == null || value.trim().isEmpty()) {
throw new CaprabankException("The " + name + " cannot be empty.");
}
}
}