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 PassiveReplicatedBankServer 
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(PassiveReplicatedBankServer.class);
66  
67    
68    ////////////////////////////////////////////////////////////////////////////////////////////
69    // Fields
70    ////////////////////////////////////////////////////////////////////////////////////////////
71  
72    private static final long serialVersionUID = -3694531037285472061L;
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 PassiveReplicatedBankServer(String[] configArgs, LifeCycle lifeCycle) 
109     throws Exception
110   {
111     name = System.getProperty("bank.name", "Bank/TheLoanBox");
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   ////////////////////////////////////////////////////////////////////////////////////////////
197   // Methods from TransactionParticipant
198   ////////////////////////////////////////////////////////////////////////////////////////////
199 
200   /* (non-Javadoc)
201    * @see net.jini.core.transaction.server.TransactionParticipant#prepare(net.jini.core.transaction.server.TransactionManager, long)
202    */
203   @Anycast
204   public int prepare(TransactionManager mgr, long id) 
205     throws UnknownTransactionException, RemoteException
206   {
207     log.debug("Bank service reached PREPARE state, txn id:" + id);
208     try {
209       itp.txnPrepared(id, id);
210     } catch (Exception e) {
211       e.printStackTrace();
212     }
213     return TransactionConstants.PREPARED;
214   }
215 
216 
217   /* (non-Javadoc)
218    * @see net.jini.core.transaction.server.TransactionParticipant#commit(net.jini.core.transaction.server.TransactionManager, long)
219    */
220   @Anycast
221   public void commit(TransactionManager mgr, long id) 
222     throws UnknownTransactionException, RemoteException
223   {
224     log.debug("Bank service reached COMMIT state, txn id: " + id);
225  
226 //    CreditDebit temp = pendingTxns.get(new Long(id));
227 //    if (temp != null)
228 //      doCreditDebit(temp.accNumber, temp.credit, temp.debit);
229     try {
230       itp.txnCompleted(id, TransactionConstants.COMMITTED);
231     } catch (Exception e) {
232       e.printStackTrace();
233     }
234     
235 //    pendingTxns.remove(new Long(id));
236 //    log.debug("commit(): New size of pendingTxns: " + pendingTxns.size());
237   }
238 
239 
240   /* (non-Javadoc)
241    * @see net.jini.core.transaction.server.TransactionParticipant#abort(net.jini.core.transaction.server.TransactionManager, long)
242    */
243   @Leadercast
244   public void abort(TransactionManager mgr, long id) 
245     throws UnknownTransactionException, RemoteException
246   {
247     log.debug("Bank service reached ABORT state");
248     
249     CreditDebit temp = pendingTxns.get(new Long(id));
250     log.debug(temp.toString());
251     try {
252       itp.txnCompleted(id, TransactionConstants.ABORTED);
253     } catch (Exception e) {
254       e.printStackTrace();
255     }
256     pendingTxns.remove(new Long(id));
257   }
258 
259 
260   /* (non-Javadoc)
261    * @see net.jini.core.transaction.server.TransactionParticipant#prepareAndCommit(net.jini.core.transaction.server.TransactionManager, long)
262    */
263   @Leadercast
264   public int prepareAndCommit(TransactionManager mgr, long id) 
265     throws UnknownTransactionException, RemoteException
266   {
267     int result = prepare(mgr, id);
268     if (result == TransactionConstants.PREPARED) {
269       commit(mgr, id);
270       result = TransactionConstants.COMMITTED;
271     }
272     return result;    
273   }
274 
275 
276   ////////////////////////////////////////////////////////////////////////////////////////////
277   // Database methods
278   ////////////////////////////////////////////////////////////////////////////////////////////
279   
280   /**
281    * Initialize database. Get properties from objectdb.xml.
282    */
283   private void initDB() 
284   {   
285     String syspmf = System.getProperty("javax.jdo.PersistenceManagerFactoryClass",
286     "com.objectdb.jdo.PMF");
287 
288     String connectionURL = System.getProperty("bank.connectionURL", "/tmp/bank.odb");
289     
290     log.debug("syspmf: " + syspmf);
291     log.debug("connectionURL: " + connectionURL);
292 
293     Properties properties = new Properties();
294     properties.setProperty(
295         "javax.jdo.PersistenceManagerFactoryClass", syspmf);
296     properties.setProperty(
297         "javax.jdo.option.ConnectionURL", connectionURL); 
298 
299     pmf = JDOHelper.getPersistenceManagerFactory(properties, 
300                                                     JDOHelper.class.getClassLoader());          
301   
302     /** If db does not contain data, insert some default accounts and balances. */
303     Account account = null;
304     account = getAccount(pm, 111111111);
305     if (account == null) {
306       for (int i=1; i < 10; i++) {
307         insertAccount(pm, 111111111*i, 100000);
308       }
309     }
310   }
311   
312   /**
313    * Insert an account object into database.
314    * 
315    * @param pm The persistence manager to use
316    * @param number The account number
317    * @param balance The balance of the account
318    */
319   private void insertAccount(PersistenceManager pm, long number, int balance) 
320   {
321     Account account = new Account(number, balance);
322     try {
323       pm = pmf.getPersistenceManager();
324       pm.currentTransaction().begin();
325       pm.makePersistent(account);
326       pm.currentTransaction().commit();
327     } catch (Exception e) {
328       pm.currentTransaction().rollback();
329       log.error(e.getMessage());
330     } finally {
331       pm.close();
332     }
333   }
334   
335   /**
336    * Update an account object.
337    * 
338    * @param pm The persistence manager to use
339    * @param number The account number to update
340    * @param balance The new balance of the account
341    */
342   private boolean updateAccount(PersistenceManager pm, long number, int balance)
343   {
344     Account account = null;
345     boolean update = true;
346     
347     try {
348       pm = pmf.getPersistenceManager();
349       pm.currentTransaction().begin();
350       Query query = pm.newQuery(Account.class, "number == " + number);
351       
352       Collection result = (Collection) query.execute();
353       
354       account = (Account) result.iterator().next();
355       
356       if (account == null) {
357         update = false;
358         log.debug("Did not find a matching instance");
359         pm.currentTransaction().rollback();  
360       }
361       
362       account.setBalance(balance);
363       pm.currentTransaction().commit(); 
364     } catch (Exception e) {
365       pm.currentTransaction().rollback();
366       log.error(e.getMessage());
367     } finally {  
368         pm.close();        
369       }
370       return update;
371     }
372 
373   /**
374    * Returns an account object with given account number.
375    * 
376    * @param pm The persistence manager to use
377    * @param number Account number of account to return
378    * @return The account object
379    */
380   private Account getAccount(PersistenceManager pm, long number)
381   {
382     Account account = null;
383     
384     try {
385       pm = pmf.getPersistenceManager();
386   
387       Query query = pm.newQuery(Account.class, "number == " + number);
388   
389       Collection result = (Collection) query.execute();
390       
391       account = (Account) result.iterator().next();
392       
393       if (account == null) {
394         log.debug("Did not find a matching instance");
395         pm.currentTransaction().rollback();  
396       }
397     } catch (Exception e) {
398       log.error(e.getMessage());
399     } finally {
400       pm.close();
401     }
402     return account;
403   }
404 
405 
406   ////////////////////////////////////////////////////////////////////////////////////////////
407   // Inner classes
408   ////////////////////////////////////////////////////////////////////////////////////////////  
409   
410   /**
411    * Class that holds a temporary credit/debit operation, before doing a commit/abort operation.
412    */
413   public class CreditDebit implements Serializable
414   {
415     int credit;
416     int debit;
417     long accNumber;
418     
419     CreditDebit(long accNumber, int credit, int debit) 
420     {
421      this.accNumber = accNumber;
422      this.credit = credit;
423       this.debit = debit;
424     }
425     
426     public String toString() 
427     {
428       String temp = "CreditDebit { \n";
429       temp += "accNumber=" + accNumber + "\n";
430       temp += "credit=" + credit + "\n";
431       temp += "debit=" + debit + "\n";
432       temp += "} \n";
433       return temp;
434     }
435   }
436 
437   
438   ////////////////////////////////////////////////////////////////////////////////////////////
439   // Methods from MembershipListener
440   ////////////////////////////////////////////////////////////////////////////////////////////  
441   
442   /* (non-Javadoc)
443    * @see jgroup.core.MembershipListener#viewChange(jgroup.core.View)
444    */
445   public void viewChange(View view) 
446   {
447     log.debug("View: " + view);
448   }
449 
450 
451   /* (non-Javadoc)
452    * @see jgroup.core.MembershipListener#prepareChange()
453    */
454   public void prepareChange() 
455   {
456   }
457 
458 
459   /* (non-Javadoc)
460    * @see jgroup.core.MembershipListener#hasLeft()
461    */
462   public void hasLeft() 
463   {
464   }
465   
466   
467   ////////////////////////////////////////////////////////////////////////////////////////////
468   // Methods from InternalPassiveTransactionParticipant
469   ////////////////////////////////////////////////////////////////////////////////////////////  
470 
471   /* (non-Javadoc)
472    * @see jgroup.jini.txn.InternalPassiveTransactionParticipant#txnPrepared(long, java.lang.Object)
473    */
474   public void txnPrepared(long txnId, Object stateObj)
475     throws RemoteException {
476 //    if (!pendingTxns.containsKey(new Long(txnId))) {
477 //      CreditDebit cd = (CreditDebit) stateObj;
478 //      pendingTxns.put(new Long(txnId), cd);
479 //    }
480     log.debug("backupPrepared " + txnId);
481   }
482   
483   /* (non-Javadoc)
484    * @see jgroup.jini.txn.InternalPassiveTransactionParticipant#txnCompleted(long, int)
485    */
486   public void txnCompleted(long txnId, int outcome)
487     throws RemoteException {
488 //    if (pendingTxns.containsKey(new Long(txnId))) {
489 //      if (outcome == TransactionConstants.COMMITTED) {
490 //        CreditDebit temp = (CreditDebit) pendingTxns.get(new Long(txnId));
491 //        if (temp != null)
492 //          doCreditDebit(temp.accNumber, temp.credit, temp.debit);
493 //      }
494 //      pendingTxns.remove(new Long(txnId));
495 //    }
496     log.debug("backupCommitted" + txnId);
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   /* (non-Javadoc)
509    * @see jgroup.jini.txn.InternalPassiveTransactionParticipant#txnDeposit(long, int, long)
510    */
511   public void txnDeposit(long account, int amount, long transactionID) 
512     throws RemoteException
513   {
514     /** Transfer state here */
515   }
516   
517 } // END PassiveReplicatedBankServer
518