/*
###############################################################################
# #
# Copyright (C) 2011-2016 OpenMEAP, Inc. #
# Credits to Jonathan Schang & Rob Thacher #
# #
# Released under the LGPLv3 #
# #
# OpenMEAP is free software: you can redistribute it and/or modify #
# it under the terms of the GNU Lesser General Public License as published #
# by the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# OpenMEAP 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 Lesser General Public License for more details. #
# #
# You should have received a copy of the GNU Lesser General Public License #
# along with OpenMEAP. If not, see <http://www.gnu.org/licenses/>. #
# #
###############################################################################
*/
package com.openmeap.samples.banking.web.model;
import java.math.BigDecimal;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.util.Collections;
import java.util.Formatter;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.Date;
import java.util.UUID;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
/**
* The aim here is a simple service for a sample application
* where the focus is to demonstrate possible server-client interaction and client functionality,
* NOT to provide a complete banking service application.
* That being said, there are probably a billion-and-one design, integrity and security considerations
* that have not been made here that should be made for a "real" banking service app.
* @author schang
*/
public class BankingService {
static private BankingService instance = null;
static private Map<String,Map<Account,Transactions>> accounts = null;
static private String AUTH_SALT=UUID.randomUUID().toString();
synchronized static public BankingService getInstance() {
if( instance == null )
instance = new BankingService();
return instance;
}
synchronized public LoginResult login(String userName, String password) {
UUID authId = UUID.randomUUID();
String authToken = authId.toString()+"."+getSha1(authId.toString()+"."+AUTH_SALT);
Accounts accts = getAccounts(userName,authToken);
if( accts==null )
return null;
LoginResult toRet = new LoginResult();
toRet.setAccounts(accts);
toRet.setAuthToken(authToken);
toRet.setOwner(userName);
return toRet;
}
synchronized public Accounts getAccounts(String userName, String authToken) {
Accounts accts = new Accounts();
accts.setOwner(userName);
List<Account> acctList = accts.getAccount();
for( Map.Entry<String, Map<Account,Transactions>> ent : accounts.entrySet() ) {
if( ent.getKey().equals(userName) ) {
for( Map.Entry<Account,Transactions> acctEnt : ent.getValue().entrySet() ) {
acctList.add(acctEnt.getKey());
}
}
}
if( accts.getAccount().size()>0 )
return accts;
else return null;
}
synchronized public Transactions getTransactions(TransactionStatus status, String userName, String acctNumber, String authToken) {
Account acct = findAccount(userName,acctNumber);
Transactions trans = findAccountTransactions(userName,acctNumber);
Transactions toRet = createTransactions(acct);
for( Transaction tran : trans.getTrans() )
if( tran.getStatus().equals(status) )
toRet.getTrans().add(tran);
Collections.sort(toRet.getTrans(),new TransactionDateComparator());
return toRet;
}
synchronized public void completeAllTransactions() {
for( Map.Entry<String, Map<Account,Transactions>> ent : accounts.entrySet() ) {
for( Map.Entry<Account,Transactions> acctEnt : ent.getValue().entrySet() ) {
for( Transaction trans : acctEnt.getValue().getTrans() ) {
if( trans.getStatus().equals(TransactionStatus.PENDING) ) {
Account acct = acctEnt.getKey();
completeAccountTransaction(trans,acct);
}
}
}
}
}
synchronized public void submitAndCompleteAccountTransaction(Transaction trans, Account acct) {
submitAccountTransaction(trans,acct);
completeAccountTransaction(trans,acct);
}
synchronized public Error submitTransfer(String userName, String srcAcct, String destAcct, Date date, double amount) {
DatatypeFactory df = null;
try {
df = DatatypeFactory.newInstance();
} catch( DatatypeConfigurationException dce ) {
throw new RuntimeException(dce);
}
Account src = null, dest = null;
src = findAccount(userName, srcAcct);
dest = findAccount(userName, destAcct);
if( src==null || dest==null ) {
Error err = new Error();
err.setCode(ErrorType.PARAM_BAD);
err.setMessage("Could not find either the source or destination account.");
return err;
}
GregorianCalendar cal = new GregorianCalendar();
cal.setTimeInMillis(date.getTime());
submitTransfer(src,dest,df.newXMLGregorianCalendar(cal),amount);
return null;
}
private void submitTransfer(Account source, Account destination, XMLGregorianCalendar date, double amount) {
submitAccountTransaction(
createTransaction(
TransactionType.DEPOSIT,
date,
"xfr "+source.getType()+"("+source.getNumber()+")",
amount
),destination);
submitAccountTransaction(
createTransaction(
TransactionType.WITHDRAW,
date,
"xto "+destination.getType()+"("+destination.getNumber()+")",
amount
),source);
}
synchronized public void submitAccountTransaction(Transaction trans, Account acct) {
trans.setAcctNumber(acct.getNumber());
trans.setStatus(TransactionStatus.PENDING);
findAccountTransactions(acct.getOwner(),acct.getNumber()).getTrans().add(trans);
if( trans.getType()==TransactionType.WITHDRAW )
acct.setAvailable( acct.getAvailable().subtract(trans.getAmount()) );
else if( trans.getType()==TransactionType.DEPOSIT )
acct.setAvailable( acct.getAvailable().add(trans.getAmount()) );
}
synchronized public void completeAccountTransaction(Transaction trans, Account acct) {
trans.setStatus(TransactionStatus.COMPLETED);
if( trans.getType()==TransactionType.WITHDRAW )
acct.setPosted( acct.getPosted().subtract(trans.getAmount()) );
else if( trans.getType()==TransactionType.DEPOSIT )
acct.setPosted( acct.getPosted().add(trans.getAmount()) );
trans.setBalance(acct.getPosted());
}
private Account findAccount(String userName, String number) {
for( Map.Entry<String, Map<Account,Transactions>> ent : accounts.entrySet() ) {
if( ent.getKey().equals(userName) )
for( Map.Entry<Account,Transactions> acctEnt : ent.getValue().entrySet() ) {
if( acctEnt.getKey().getNumber().equals(number) )
return acctEnt.getKey();
}
}
return null;
}
private Transactions findAccountTransactions(String userName, String number) {
for( Map.Entry<String, Map<Account,Transactions>> ent : accounts.entrySet() ) {
if( ent.getKey().equals(userName) )
for( Map.Entry<Account,Transactions> acctEnt : ent.getValue().entrySet() ) {
if( acctEnt.getKey().getNumber().equals(number) )
return acctEnt.getValue();
}
}
return null;
}
private Transaction createTransaction(TransactionType type, XMLGregorianCalendar date, String desc, double amount) {
Transaction trans = new Transaction();
trans.setDate(date);
trans.setDesc(desc);
trans.setType(type);
trans.setAmount(BigDecimal.valueOf(amount));
return trans;
}
private Transactions createTransactions(Account acct) {
Transactions trans = new Transactions();
trans.setOwner(acct.getOwner());
trans.setAccountNumber(acct.getNumber());
return trans;
}
private Account createAccount(AccountType type, String number, String owner, double posted, double available) {
Account acct = new Account();
acct.setType(type);
acct.setNumber(number);
acct.setOwner(owner);
acct.setAvailable(BigDecimal.valueOf(available));
acct.setPosted(BigDecimal.valueOf(posted));
return acct;
}
private static String getSha1(String value) {
MessageDigest sha1;
try {
sha1 = MessageDigest.getInstance("SHA1");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
Formatter formatter = new Formatter();
for (byte b : sha1.digest(value.getBytes())) {
formatter.format("%02x", b);
}
return formatter.toString();
}
/**
* We'll just go ahead and manually create a bunch of sample data here in the service class.
*/
private BankingService() {
accounts = new HashMap<String,Map<Account,Transactions>>();
Account accountChecking = null;
Account accountSavings = null;
Transactions transactions = null;
String currentOwner = null;
Map<Account,Transactions> thisSet = null;
// create the first user and batch of accounts and transactions
{
currentOwner = "Jon Doe";
thisSet = new HashMap<Account,Transactions>();
accounts.put(currentOwner, thisSet);
DatatypeFactory df = null;
try {
df = DatatypeFactory.newInstance();
} catch( DatatypeConfigurationException dce ) {
throw new RuntimeException(dce);
}
accountChecking = createAccount(AccountType.CHECKING,"*6303",currentOwner,2533.00,2533.00);
transactions = createTransactions(accountChecking);
thisSet.put(accountChecking,transactions);
submitAccountTransaction(createTransaction(
TransactionType.DEPOSIT,
df.newXMLGregorianCalendar("2011-03-01T12:00:00+03:00"),
"Celerity",
1300.00),accountChecking );
submitAccountTransaction(createTransaction(
TransactionType.WITHDRAW,
df.newXMLGregorianCalendar("2011-03-02T12:00:00+03:00"),
"Mega Groceries, Inc.",
240.00),accountChecking);
submitAccountTransaction(createTransaction(
TransactionType.WITHDRAW,
df.newXMLGregorianCalendar("2011-03-02T12:00:00+03:00"),
"Mega Convenience Chain, Inc.",
12.00),accountChecking);
accountSavings = createAccount(AccountType.SAVINGS,"*6323",currentOwner,15533.00,15533.00);
transactions = createTransactions(accountSavings);
thisSet.put(accountSavings,transactions);
submitTransfer(accountChecking,accountSavings,df.newXMLGregorianCalendar("2011-03-01T12:00:00+03:00"),2000.00);
submitTransfer(accountSavings,accountChecking,df.newXMLGregorianCalendar("2011-03-01T12:00:00+03:00"),150.00);
submitTransfer(accountSavings,accountChecking,df.newXMLGregorianCalendar("2011-03-02T12:00:00+03:00"),150.00);
submitTransfer(accountSavings,accountChecking,df.newXMLGregorianCalendar("2011-03-03T12:00:00+03:00"),150.00);
submitTransfer(accountChecking,accountSavings,df.newXMLGregorianCalendar("2011-03-04T12:00:00+03:00"),1400.00);
completeAllTransactions();
}
}
}