View Javadoc

1   /*
2    * Copyright (c) 1998-2004 The Jgroup Team.
3    *
4    * This program is free software; you can redistribute it and/or modify
5    * it under the terms of the GNU Lesser General Public License version 2 as
6    * published by the Free Software Foundation.
7    *
8    * This program is distributed in the hope that it will be useful,
9    * but WITHOUT ANY WARRANTY; without even the implied warranty of
10   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11   * GNU Lesser General Public License for more details.
12   *
13   * You should have received a copy of the GNU Lesser General Public License
14   * along with this program; if not, write to the Free Software
15   * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16   *
17   */
18  
19  package jgroup.test.jini.txn;
20  
21  import java.io.Serializable;
22  import java.rmi.RemoteException;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.Map;
27  import java.util.Properties;
28  
29  import javax.jdo.JDOHelper;
30  import javax.jdo.PersistenceManager;
31  import javax.jdo.PersistenceManagerFactory;
32  import javax.jdo.Query;
33  
34  import jgroup.core.GroupManager;
35  import jgroup.core.MembershipListener;
36  import jgroup.core.View;
37  import jgroup.core.protocols.Anycast;
38  import jgroup.core.protocols.Leadercast;
39  import jgroup.jini.txn.InternalPassiveTransactionParticipant;
40  import net.jini.core.transaction.UnknownTransactionException;
41  import net.jini.core.transaction.server.TransactionConstants;
42  import net.jini.core.transaction.server.TransactionManager;
43  import net.jini.core.transaction.server.TransactionParticipant;
44  
45  import org.apache.log4j.Logger;
46  
47  import com.sun.jini.start.LifeCycle;
48  
49  /**
50   * Bank server with replication support used as participant in a
51   * transaction.
52   * 
53   * @author Rohnny Moland
54   */
55  public class PassiveReplicatedBankServer2 
56    implements Bank, TransactionParticipant, MembershipListener,
57               InternalPassiveTransactionParticipant
58  {
59    
60    ////////////////////////////////////////////////////////////////////////////////////////////
61    // Logger
62    ////////////////////////////////////////////////////////////////////////////////////////////
63  
64    /** Obtain logger for this class */
65    private static final Logger log = Logger.getLogger(PassiveReplicatedBankServer2.class);
66  
67    
68    ////////////////////////////////////////////////////////////////////////////////////////////
69    // Fields
70    ////////////////////////////////////////////////////////////////////////////////////////////
71  
72    private static final long serialVersionUID = 1618846528168458831L;
73  
74    /** A reference to the IGMI group */
75    private InternalPassiveTransactionParticipant itp;
76  
77    /** Maps used to store pending transactions and the bank accounts. */
78    private Map<Long, CreditDebit> pendingTxns;
79    private Map<Integer, Integer> accounts;
80    
81    /** Name of the bank service */
82    private String name; 
83  
84    /** Database specific fields */
85    private PersistenceManagerFactory pmf;
86    private PersistenceManager pm;
87  
88    
89    ////////////////////////////////////////////////////////////////////////////////////////////
90    // Constructor and main
91    ////////////////////////////////////////////////////////////////////////////////////////////
92  
93    /**
94     * Constructor compatible with the jini service starter.
95     *
96     * @param configArgs <code>String</code> array whose elements are
97     *                   the arguments to use when creating the server.
98     * @param lifeCycle  instance of <code>LifeCycle</code> that, if
99     *                   non-<code>null</code>, will cause this object's
100    *                   <code>unregister</code> method to be invoked during
101    *                   shutdown to notify the service starter framework that
102    *                   the reference to this service's implementation can be
103    *                   'released' for garbage collection. A value of
104    *                   <code>null</code> for this argument is allowed.
105    *
106    * @throws Exception If there was a problem initializing the service.
107    */
108   public PassiveReplicatedBankServer2(String[] configArgs, LifeCycle lifeCycle) 
109     throws Exception
110   {
111     name = System.getProperty("bank.name", "Bank/LargeFees");
112   
113     pendingTxns = Collections.synchronizedMap(new HashMap<Long, CreditDebit>());
114     accounts = Collections.synchronizedMap(new HashMap<Integer, Integer>() );
115     
116     GroupManager gm = GroupManager.getGroupManager(this);
117     itp = (InternalPassiveTransactionParticipant) gm.getService(InternalPassiveTransactionParticipant.class);
118     
119     initDB();  
120    }
121 
122 
123   ////////////////////////////////////////////////////////////////////////////////////////////
124   // Methods from Bank
125   ////////////////////////////////////////////////////////////////////////////////////////////
126    
127   /* (non-Javadoc)
128    * @see jgroup.test.jini.txn.Bank#getBalance(long)
129    */
130   @Anycast
131   public Integer getBalance(long accNumber) 
132     throws RemoteException
133   {
134     Integer balance = accounts.get(new Long(accNumber));
135     if (balance == null) balance = new Integer(0); 
136     return balance;
137   }
138   
139   
140   /* (non-Javadoc)
141    * @see jgroup.test.jini.txn.Bank#withdraw(long, int, net.jini.core.transaction.Transaction)
142    */
143   @Anycast
144   public void withdraw(long accountNumber, int amount, long transactionID)
145     throws RemoteException
146   {
147     pendingTxns.put(new Long(transactionID), new CreditDebit(accountNumber, 0, amount));
148     log.debug("Withdraw " + amount + " on " + name + " with account number " + accountNumber);
149   }
150 
151 
152   /* (non-Javadoc)
153    * @see jgroup.test.jini.txn.Bank#deposit(long, int, net.jini.core.transaction.Transaction)
154    */
155   @Anycast
156   public void deposit(long accountNumber, int amount, long transactionID)
157     throws RemoteException
158   {
159     pendingTxns.put(new Long(transactionID), new CreditDebit(accountNumber, amount, 0));
160     log.debug("Deposit " + amount + " on " + name + " with account number " + accountNumber);
161   }
162 
163   
164   /* (non-Javadoc)
165    * @see jgroup.test.jini.txn.Bank#getName()
166    */
167   @Anycast
168   public String getName()
169     throws RemoteException
170   {
171     return name;
172   }
173 
174 
175   /**
176    * Do the credit/debit operation.
177    * 
178    * @param accNumber Account number to credit/debit
179    * @param credit Amount to credit
180    * @param debit Amount to debit
181    */
182   private void doCreditDebit(long accNumber, int credit, int debit)
183   { 
184 //    log.debug("credit = " + credit + " debit = " + debit);
185     Account account = getAccount(pm, accNumber);
186 //    log.debug("Balance BEFORE commit for acc.nr " + accNumber + " is " + account.getBalance());
187     int newBalance = account.getBalance();
188     newBalance += credit;
189     newBalance -= debit;
190     updateAccount(pm, accNumber, newBalance);    
191 //    log.debug("Balance AFTER commit for acc.nr " + accNumber + " is " + account.getBalance());
192   }
193  
194   
195   ////////////////////////////////////////////////////////////////////////////////////////////
196   // Methods from TransactionParticipant
197   ////////////////////////////////////////////////////////////////////////////////////////////
198 
199   /* (non-Javadoc)
200    * @see net.jini.core.transaction.server.TransactionParticipant#prepare(net.jini.core.transaction.server.TransactionManager, long)
201    */
202   @Anycast
203   public int prepare(TransactionManager mgr, long id) 
204     throws UnknownTransactionException, RemoteException
205   {
206     log.debug("Bank service reached PREPARE state, txn id:" + id);
207     try {
208       itp.txnPrepared(id, id);
209     } catch (Exception e) {
210       e.printStackTrace();
211     }
212     return TransactionConstants.PREPARED;
213   }
214 
215 
216   /* (non-Javadoc)
217    * @see net.jini.core.transaction.server.TransactionParticipant#commit(net.jini.core.transaction.server.TransactionManager, long)
218    */
219   @Anycast 
220   public void commit(TransactionManager mgr, long id) 
221     throws UnknownTransactionException, RemoteException
222   {
223     log.debug("Bank service reached COMMIT state, txn id: " + id);
224  
225 //    CreditDebit temp = pendingTxns.get(new Long(id));
226 //    if (temp != null)
227 //      doCreditDebit(temp.accNumber, temp.credit, temp.debit);
228     try {
229       itp.txnCompleted(id, TransactionConstants.COMMITTED);
230     } catch (Exception e) {
231       e.printStackTrace();
232     }
233     
234 //    pendingTxns.remove(new Long(id));
235 //    log.debug("commit(): New size of pendingTxns: " + pendingTxns.size());
236   }
237 
238 
239   /* (non-Javadoc)
240    * @see net.jini.core.transaction.server.TransactionParticipant#abort(net.jini.core.transaction.server.TransactionManager, long)
241    */
242   @Leadercast 
243   public void abort(TransactionManager mgr, long id) 
244     throws UnknownTransactionException, RemoteException
245   {
246     log.debug("Bank service reached ABORT state");
247     
248     CreditDebit temp = pendingTxns.get(new Long(id));
249     log.debug(temp.toString());
250     try {
251       itp.txnCompleted(id, TransactionConstants.ABORTED);
252     } catch (Exception e) {
253       e.printStackTrace();
254     }
255     pendingTxns.remove(new Long(id));
256   }
257 
258 
259   /* (non-Javadoc)
260    * @see net.jini.core.transaction.server.TransactionParticipant#prepareAndCommit(net.jini.core.transaction.server.TransactionManager, long)
261    */
262   @Leadercast 
263   public int prepareAndCommit(TransactionManager mgr, long id) 
264     throws UnknownTransactionException, RemoteException
265   {
266     int result = prepare(mgr, id);
267     if (result == TransactionConstants.PREPARED) {
268       commit(mgr, id);
269       result = TransactionConstants.COMMITTED;
270     }
271     return result;    
272   }
273 
274 
275   ////////////////////////////////////////////////////////////////////////////////////////////
276   // Database methods
277   ////////////////////////////////////////////////////////////////////////////////////////////
278   
279   /**
280    * Initialize database. Get properties from objectdb.xml.
281    */
282   private void initDB() 
283   {   
284     String syspmf = System.getProperty("javax.jdo.PersistenceManagerFactoryClass",
285     "com.objectdb.jdo.PMF");
286 
287     String connectionURL = System.getProperty("bank.connectionURL", "/tmp/bank.odb");
288     
289     log.debug("syspmf: " + syspmf);
290     log.debug("connectionURL: " + connectionURL);
291 
292     Properties properties = new Properties();
293     properties.setProperty(
294         "javax.jdo.PersistenceManagerFactoryClass", syspmf);
295     properties.setProperty(
296         "javax.jdo.option.ConnectionURL", connectionURL); 
297 
298     pmf = JDOHelper.getPersistenceManagerFactory(properties, 
299                                                     JDOHelper.class.getClassLoader());          
300   
301     /** If db does not contain data, insert some default accounts and balances. */
302     Account account = null;
303     account = getAccount(pm, 111111111);
304     if (account == null) {
305       for (int i=1; i < 10; i++) {
306         insertAccount(pm, 111111111*i, 100000);
307       }
308     }
309   }
310   
311   /**
312    * Insert an account object into database.
313    * 
314    * @param pm The persistence manager to use
315    * @param number The account number
316    * @param balance The balance of the account
317    */
318   private void insertAccount(PersistenceManager pm, long number, int balance) 
319   {
320     Account account = new Account(number, balance);
321     try {
322       pm = pmf.getPersistenceManager();
323       pm.currentTransaction().begin();
324       pm.makePersistent(account);
325       pm.currentTransaction().commit();
326     } catch (Exception e) {
327       pm.currentTransaction().rollback();
328       log.error(e.getMessage());
329     } finally {
330       pm.close();
331     }
332   }
333   
334   /**
335    * Update an account object.
336    * 
337    * @param pm The persistence manager to use
338    * @param number The account number to update
339    * @param balance The new balance of the account
340    */
341   private boolean updateAccount(PersistenceManager pm, long number, int balance)
342   {
343     Account account = null;
344     boolean update = true;
345     
346     try {
347       pm = pmf.getPersistenceManager();
348       pm.currentTransaction().begin();
349       Query query = pm.newQuery(Account.class, "number == " + number);
350       
351       Collection result = (Collection) query.execute();
352       
353       account = (Account) result.iterator().next();
354       
355       if (account == null) {
356         update = false;
357         log.debug("Did not find a matching instance");
358         pm.currentTransaction().rollback();  
359       }
360       
361       account.setBalance(balance);
362       pm.currentTransaction().commit(); 
363     } catch (Exception e) {
364       pm.currentTransaction().rollback();
365       log.error(e.getMessage());
366     } finally {  
367         pm.close();        
368       }
369       return update;
370     }
371 
372   /**
373    * Returns an account object with given account number.
374    * 
375    * @param pm The persistence manager to use
376    * @param number Account number of account to return
377    * @return The account object
378    */
379   private Account getAccount(PersistenceManager pm, long number)
380   {
381     Account account = null;
382     
383     try {
384       pm = pmf.getPersistenceManager();
385   
386       Query query = pm.newQuery(Account.class, "number == " + number);
387   
388       Collection result = (Collection) query.execute();
389       
390       account = (Account) result.iterator().next();
391       
392       if (account == null) {
393         log.debug("Did not find a matching instance");
394         pm.currentTransaction().rollback();  
395       }
396     } catch (Exception e) {
397       log.error(e.getMessage());
398     } finally {
399       pm.close();
400     }
401     return account;
402   }
403 
404 
405   ////////////////////////////////////////////////////////////////////////////////////////////
406   // Inner classes
407   ////////////////////////////////////////////////////////////////////////////////////////////  
408   
409   /**
410    * Class that holds a temporary credit/debit operation, before doing a commit/abort operation.
411    */
412   public class CreditDebit implements Serializable
413   {
414     int credit;
415     int debit;
416     long accNumber;
417     
418     CreditDebit(long accNumber, int credit, int debit) 
419     {
420      this.accNumber = accNumber;
421      this.credit = credit;
422       this.debit = debit;
423     }
424     
425     public String toString() 
426     {
427       String temp = "CreditDebit { \n";
428       temp += "accNumber=" + accNumber + "\n";
429       temp += "credit=" + credit + "\n";
430       temp += "debit=" + debit + "\n";
431       temp += "} \n";
432       return temp;
433     }
434   }
435 
436   
437   ////////////////////////////////////////////////////////////////////////////////////////////
438   // Methods from MembershipListener
439   ////////////////////////////////////////////////////////////////////////////////////////////  
440   
441   /* (non-Javadoc)
442    * @see jgroup.core.MembershipListener#viewChange(jgroup.core.View)
443    */
444   public void viewChange(View view) 
445   {
446     log.debug("View: " + view);
447   }
448 
449 
450   /* (non-Javadoc)
451    * @see jgroup.core.MembershipListener#prepareChange()
452    */
453   public void prepareChange() 
454   {
455   }
456 
457 
458   /* (non-Javadoc)
459    * @see jgroup.core.MembershipListener#hasLeft()
460    */
461   public void hasLeft() 
462   {
463   }
464   
465   
466   ////////////////////////////////////////////////////////////////////////////////////////////
467   // Methods from InternalPassiveTransactionParticipant
468   ////////////////////////////////////////////////////////////////////////////////////////////  
469 
470   /* (non-Javadoc)
471    * @see jgroup.jini.txn.InternalPassiveTransactionParticipant#txnPrepared(long, java.lang.Object)
472    */
473   public void txnPrepared(long txnId, Object stateObj)
474     throws RemoteException {
475 //    if (!pendingTxns.containsKey(new Long(txnId))) {
476 //      CreditDebit cd = (CreditDebit) stateObj;
477 //      pendingTxns.put(new Long(txnId), cd);
478 //    }
479     log.debug("backupPrepared " + txnId);
480   }
481   
482   /* (non-Javadoc)
483    * @see jgroup.jini.txn.InternalPassiveTransactionParticipant#txnCompleted(long, int)
484    */
485   public void txnCompleted(long TxnId, int outcome)
486     throws RemoteException {
487 //    if (pendingTxns.containsKey(new Long(TxnId))) {
488 //      if (outcome == TransactionConstants.COMMITTED) {
489 //        CreditDebit temp = (CreditDebit) pendingTxns.get(new Long(TxnId));
490 //        if (temp != null)
491 //          doCreditDebit(temp.accNumber, temp.credit, temp.debit);
492 //      }
493 //      pendingTxns.remove(new Long(TxnId));
494 //    }
495     log.debug("backupCommitted" + TxnId);
496    }
497 
498 
499   /* (non-Javadoc)
500    * @see jgroup.jini.txn.InternalPassiveTransactionParticipant#txnWithdraw(long, int, long)
501    */
502   public void txnWithdraw(long account, int amount, long transactionID) 
503     throws RemoteException
504   {
505     /** Transfer state here */
506   }
507 
508 
509   /* (non-Javadoc)
510    * @see jgroup.jini.txn.InternalPassiveTransactionParticipant#txnDeposit(long, int, long)
511    */
512   public void txnDeposit(long account, int amount, long transactionID) 
513     throws RemoteException
514   {
515     /** Transfer state here */
516   }
517   
518 } // END PassiveReplicatedBankServer2
519