View Javadoc

1   /*
2    * Copyright (c) 1998-2006 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  package jgroup.test.jini.txn;
19  
20  import java.io.Serializable;
21  import java.rmi.RemoteException;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.Map;
26  import java.util.Properties;
27  
28  import javax.jdo.JDOHelper;
29  import javax.jdo.PersistenceManager;
30  import javax.jdo.PersistenceManagerFactory;
31  import javax.jdo.Query;
32  
33  import jgroup.core.GroupManager;
34  import jgroup.core.MembershipListener;
35  import jgroup.core.View;
36  import jgroup.core.protocols.Anycast;
37  import jgroup.core.protocols.Atomic;
38  import net.jini.core.transaction.UnknownTransactionException;
39  import net.jini.core.transaction.server.TransactionConstants;
40  import net.jini.core.transaction.server.TransactionManager;
41  import net.jini.core.transaction.server.TransactionParticipant;
42  
43  import org.apache.log4j.Logger;
44  
45  import com.sun.jini.start.LifeCycle;
46  
47  /**
48   * Bank server with replication support used as participant in a
49   * transaction.
50   * 
51   * @author Rohnny Moland
52   */
53  public class ReplicatedBankServer 
54    implements Bank, TransactionParticipant, MembershipListener
55  {
56    ////////////////////////////////////////////////////////////////////////////////////////////
57    // Logger
58    ////////////////////////////////////////////////////////////////////////////////////////////
59  
60    private static final Logger log = Logger.getLogger(ReplicatedBankServer.class);
61  
62    
63    ////////////////////////////////////////////////////////////////////////////////////////////
64    // Fields
65    ////////////////////////////////////////////////////////////////////////////////////////////
66  
67    /** Maps used to store pending transactions and the bank accounts. */
68    private Map<Long, CreditDebit> pendingTxns;
69    private Map<Integer, Integer> accounts;
70    
71    /** Name of the bank service */
72    private String name; 
73  
74    /** Database specific fields */
75    private PersistenceManagerFactory pmf;
76    private PersistenceManager pm;
77  
78    
79    ////////////////////////////////////////////////////////////////////////////////////////////
80    // Constructor and main
81    ////////////////////////////////////////////////////////////////////////////////////////////
82  
83    /**
84     * Constructor compatible with the jini service starter.
85     *
86     * @param configArgs <code>String</code> array whose elements are
87     *                   the arguments to use when creating the server.
88     * @param lifeCycle  instance of <code>LifeCycle</code> that, if
89     *                   non-<code>null</code>, will cause this object's
90     *                   <code>unregister</code> method to be invoked during
91     *                   shutdown to notify the service starter framework that
92     *                   the reference to this service's implementation can be
93     *                   'released' for garbage collection. A value of
94     *                   <code>null</code> for this argument is allowed.
95     *
96     * @throws Exception If there was a problem initializing the service.
97     */
98    public ReplicatedBankServer(String[] configArgs, LifeCycle lifeCycle) 
99      throws Exception
100   {    
101     name = System.getProperty("bank.name", "NOKAS/RobberyBank");
102   
103     pendingTxns = Collections.synchronizedMap(new HashMap<Long, CreditDebit>());
104     accounts = Collections.synchronizedMap(new HashMap<Integer, Integer>() );
105     
106     @SuppressWarnings("unused") 
107     GroupManager gm = GroupManager.getGroupManager(this);
108     
109     initDB();  
110    }
111 
112 
113   ////////////////////////////////////////////////////////////////////////////////////////////
114   // Methods from Bank
115   ////////////////////////////////////////////////////////////////////////////////////////////
116    
117   /* (non-Javadoc)
118    * @see jgroup.test.jini.txn.Bank#getBalance(long)
119    */
120   @Anycast 
121   public Integer getBalance(long accNumber) 
122     throws RemoteException
123   {
124     Integer balance = accounts.get(new Long(accNumber));
125     if (balance == null) balance = new Integer(0); 
126     return balance;
127   }
128   
129   
130   /* (non-Javadoc)
131    * @see jgroup.test.jini.txn.Bank#withdraw(long, int, net.jini.core.transaction.Transaction)
132    */
133   /**
134    * This method should be atomic or using leadercast.
135    */
136   @Atomic
137   public void withdraw(long accountNumber, int amount, long transactionID)
138     throws RemoteException
139   {
140 //    pendingTxns.put(new Long(transactionID), new CreditDebit(accountNumber, 0, amount));
141     log.debug("Withdraw=" + amount + ",Account=" + accountNumber + ",tid=" + transactionID);
142   }
143 
144 
145   /* (non-Javadoc)
146    * @see jgroup.test.jini.txn.Bank#deposit(long, int, net.jini.core.transaction.Transaction)
147    */
148   @Atomic 
149   public void deposit(long accountNumber, int amount, long transactionID)
150     throws RemoteException
151   {
152 //    pendingTxns.put(new Long(transactionID), new CreditDebit(accountNumber, amount, 0));
153     log.debug("Deposit=" + amount + ",Account=" + accountNumber + ",tid=" + transactionID);
154   }
155 
156   
157   /* (non-Javadoc)
158    * @see jgroup.test.jini.txn.Bank#getName()
159    */
160   @Anycast 
161   public String getName()
162     throws RemoteException
163   {
164     return name;
165   }
166 
167 
168   /**
169    * Do the credit/debit operation.
170    * 
171    * @param accNumber Account number to credit/debit
172    * @param credit Amount to credit
173    * @param debit Amount to debit
174    */
175   private void doCreditDebit(long accNumber, int credit, int debit)
176   { 
177 //    log.debug("credit = " + credit + " debit = " + debit);
178     Account account = getAccount(pm, accNumber);
179 //    log.debug("Balance BEFORE commit for acc.nr " + accNumber + " is " + account.getBalance());
180     int newBalance = account.getBalance();
181     newBalance += credit;
182     newBalance -= debit;
183     updateAccount(pm, accNumber, newBalance);    
184 //    log.debug("Balance AFTER commit for acc.nr " + accNumber + " is " + account.getBalance());
185 
186   }
187  
188   
189   ////////////////////////////////////////////////////////////////////////////////////////////
190   // Methods from TransactionParticipant
191   ////////////////////////////////////////////////////////////////////////////////////////////
192 
193   /* (non-Javadoc)
194    * @see net.jini.core.transaction.server.TransactionParticipant#prepare(net.jini.core.transaction.server.TransactionManager, long)
195    */
196   @Atomic
197   public int prepare(TransactionManager mgr, long id) 
198     throws UnknownTransactionException, RemoteException
199   {
200     log.debug(name + " reached PREPARE state, txn id:" + id);
201     return TransactionConstants.PREPARED;
202   }
203 
204 
205   /* (non-Javadoc)
206    * @see net.jini.core.transaction.server.TransactionParticipant#commit(net.jini.core.transaction.server.TransactionManager, long)
207    */
208   @Atomic
209   public void commit(TransactionManager mgr, long id) 
210     throws UnknownTransactionException, RemoteException
211   {
212     log.debug(name + " reached COMMIT state, txn id: " + id);
213  
214 //    CreditDebit temp = pendingTxns.get(new Long(id));
215 //    if (temp != null)
216 //      doCreditDebit(temp.accNumber, temp.credit, temp.debit);
217 //    
218 //    pendingTxns.remove(new Long(id));
219 //    log.debug("commit(): New size of pendingTxns: " + pendingTxns.size());
220   }
221 
222 
223   /* (non-Javadoc)
224    * @see net.jini.core.transaction.server.TransactionParticipant#abort(net.jini.core.transaction.server.TransactionManager, long)
225    */
226   @Atomic 
227   public void abort(TransactionManager mgr, long id) 
228     throws UnknownTransactionException, RemoteException
229   {
230     log.debug(name + " reached ABORT state, txn id: " + id);
231     
232 //    CreditDebit temp = pendingTxns.get(new Long(id));
233 //    log.debug(temp.toString());
234 //    pendingTxns.remove(new Long(id));
235   }
236 
237 
238   /* (non-Javadoc)
239    * @see net.jini.core.transaction.server.TransactionParticipant#prepareAndCommit(net.jini.core.transaction.server.TransactionManager, long)
240    */
241   @Atomic 
242   public int prepareAndCommit(TransactionManager mgr, long id) 
243     throws UnknownTransactionException, RemoteException
244   {
245     int result = prepare(mgr, id);
246     if (result == TransactionConstants.PREPARED) {
247       commit(mgr, id);
248       result = TransactionConstants.COMMITTED;
249     }
250     return result;    
251   }
252 
253 
254   ////////////////////////////////////////////////////////////////////////////////////////////
255   // Database methods
256   ////////////////////////////////////////////////////////////////////////////////////////////
257   
258   /**
259    * Initialize database. Get properties from objectdb.xml.
260    */
261   private void initDB() 
262   {   
263     String syspmf = System.getProperty("javax.jdo.PersistenceManagerFactoryClass",
264     "com.objectdb.jdo.PMF");
265 
266     String connectionURL = System.getProperty("bank.connectionURL", "/tmp/bank.odb");
267     
268     log.debug("syspmf: " + syspmf);
269     log.debug("connectionURL: " + connectionURL);
270 
271     Properties properties = new Properties();
272     properties.setProperty(
273         "javax.jdo.PersistenceManagerFactoryClass", syspmf);
274     properties.setProperty(
275         "javax.jdo.option.ConnectionURL", connectionURL); 
276 
277     pmf = JDOHelper.getPersistenceManagerFactory(properties, 
278                                                     JDOHelper.class.getClassLoader());          
279   
280     /** If db does not contain data, insert some default accounts and balances. */
281     Account account = null;
282     account = getAccount(pm, 111111111);
283     if (account == null) {
284       for (int i=1; i < 10; i++) {
285         insertAccount(pm, 111111111*i, 100000);
286       }
287     }
288   }
289 
290 
291   /**
292    * Insert an account object into database.
293    * 
294    * @param pm The persistence manager to use
295    * @param number The account number
296    * @param balance The balance of the account
297    */
298   private void insertAccount(PersistenceManager pm, long number, int balance) 
299   {
300     Account account = new Account(number, balance);
301     try {
302       pm = pmf.getPersistenceManager();
303       pm.currentTransaction().begin();
304       pm.makePersistent(account);
305       pm.currentTransaction().commit();
306     } catch (Exception e) {
307       pm.currentTransaction().rollback();
308       log.error(e.getMessage());
309     } finally {
310       pm.close();
311     }
312   }
313   
314 
315   /**
316    * Update an account object.
317    * 
318    * @param pm The persistence manager to use
319    * @param number The account number to update
320    * @param balance The new balance of the account
321    */
322   private boolean updateAccount(PersistenceManager pm, long number, int balance)
323   {
324     Account account = null;
325     boolean update = true;
326     
327     try {
328       pm = pmf.getPersistenceManager();
329       pm.currentTransaction().begin();
330       Query query = pm.newQuery(Account.class, "number == " + number);
331       Collection result = (Collection) query.execute();      
332       account = (Account) result.iterator().next();
333       
334       if (account == null) {
335         update = false;
336         log.debug("Did not find a matching instance");
337         pm.currentTransaction().rollback();  
338       }
339       
340       account.setBalance(balance);
341       pm.currentTransaction().commit(); 
342     } catch (Exception e) {
343       pm.currentTransaction().rollback();
344       log.error(e.getMessage());
345     } finally {  
346         pm.close();        
347       }
348       return update;
349     }
350 
351 
352   /**
353    * Returns an account object with given account number.
354    * 
355    * @param pm The persistence manager to use
356    * @param number Account number of account to return
357    * @return The account object
358    */
359   private Account getAccount(PersistenceManager pm, long number)
360   {
361     Account account = null;
362     
363     try {
364       pm = pmf.getPersistenceManager();
365   
366       Query query = pm.newQuery(Account.class, "number == " + number);
367   
368       Collection result = (Collection) query.execute();
369       
370       account = (Account) result.iterator().next();
371       
372       if (account == null) {
373         log.debug("Did not find a matching instance");
374         pm.currentTransaction().rollback();  
375       }
376     } catch (Exception e) {
377       log.error(e.getMessage());
378     } finally {
379       pm.close();
380     }
381     return account;
382   }
383 
384 
385   ////////////////////////////////////////////////////////////////////////////////////////////
386   // Inner classes
387   ////////////////////////////////////////////////////////////////////////////////////////////  
388   
389   /**
390    * Class that holds a temporary credit/debit operation, before doing a commit/abort operation.
391    */
392   public class CreditDebit implements Serializable
393   {
394     private static final long serialVersionUID = 2788312247739832519L;
395     int credit;
396     int debit;
397     long accNumber;
398     
399     CreditDebit(long accNumber, int credit, int debit) 
400     {
401      this.accNumber = accNumber;
402      this.credit = credit;
403       this.debit = debit;
404     }
405     
406     public String toString() 
407     {
408       String temp = "CreditDebit { \n";
409       temp += "accNumber=" + accNumber + "\n";
410       temp += "credit=" + credit + "\n";
411       temp += "debit=" + debit + "\n";
412       temp += "} \n";
413       return temp;
414     }
415   }
416 
417   
418   ////////////////////////////////////////////////////////////////////////////////////////////
419   // Methods from MembershipListener
420   ////////////////////////////////////////////////////////////////////////////////////////////  
421   
422   /* (non-Javadoc)
423    * @see jgroup.core.MembershipListener#viewChange(jgroup.core.View)
424    */
425   public void viewChange(View view) 
426   {
427     log.debug("View: " + view);
428   }
429 
430 
431   /* (non-Javadoc)
432    * @see jgroup.core.MembershipListener#prepareChange()
433    */
434   public void prepareChange() 
435   {
436   }
437 
438 
439   /* (non-Javadoc)
440    * @see jgroup.core.MembershipListener#hasLeft()
441    */
442   public void hasLeft() 
443   {
444   }
445   
446 } // END ReplicatedBankServer