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 ReplicatedBankServer2 
54    implements Bank, TransactionParticipant, MembershipListener
55  {
56    ////////////////////////////////////////////////////////////////////////////////////////////
57    // Logger
58    ////////////////////////////////////////////////////////////////////////////////////////////
59  
60    private static final Logger log = Logger.getLogger(ReplicatedBankServer2.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 ReplicatedBankServer2(String[] configArgs, LifeCycle lifeCycle) 
99      throws Exception
100   {
101     name = System.getProperty("bank.name", "Bank/YadaSavings");
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   @Atomic
134   public void withdraw(long accountNumber, int amount, long transactionID)
135     throws RemoteException
136   {
137 //    pendingTxns.put(new Long(transactionID), new CreditDebit(accountNumber, 0, amount));
138     log.debug("Withdraw=" + amount + ",Account=" + accountNumber + ",tid=" + transactionID);
139   }
140 
141 
142   /* (non-Javadoc)
143    * @see jgroup.test.jini.txn.Bank#deposit(long, int, net.jini.core.transaction.Transaction)
144    */
145   @Atomic
146   public void deposit(long accountNumber, int amount, long transactionID)
147     throws RemoteException
148   {
149 //    pendingTxns.put(new Long(transactionID), new CreditDebit(accountNumber, amount, 0));
150     log.debug("Deposit=" + amount + ",Account=" + accountNumber + ",tid=" + transactionID);
151   }
152 
153   
154   /* (non-Javadoc)
155    * @see jgroup.test.jini.txn.Bank#getName()
156    */
157   @Anycast 
158   public String getName()
159     throws RemoteException
160   {
161     return name;
162   }
163 
164 
165   /**
166    * Do the credit/debit operation.
167    * 
168    * @param accNumber Account number to credit/debit
169    * @param credit Amount to credit
170    * @param debit Amount to debit
171    */
172   private void doCreditDebit(long accNumber, int credit, int debit)
173   { 
174 //    log.debug("credit = " + credit + " debit = " + debit);
175     Account account = getAccount(pm, accNumber);
176 //    log.debug("Balance BEFORE commit for acc.nr " + accNumber + " is " + account.getBalance());
177     int newBalance = account.getBalance();
178     newBalance += credit;
179     newBalance -= debit;
180     updateAccount(pm, accNumber, newBalance);    
181 //    log.debug("Balance AFTER commit for acc.nr " + accNumber + " is " + account.getBalance());
182 
183   }
184  
185   
186   ////////////////////////////////////////////////////////////////////////////////////////////
187   // Methods from TransactionParticipant
188   ////////////////////////////////////////////////////////////////////////////////////////////
189 
190   /* (non-Javadoc)
191    * @see net.jini.core.transaction.server.TransactionParticipant#prepare(net.jini.core.transaction.server.TransactionManager, long)
192    */
193   @Atomic 
194   public int prepare(TransactionManager mgr, long id) 
195     throws UnknownTransactionException, RemoteException
196   {
197     log.debug(name + " reached PREPARE state, txn id:" + id);
198     return TransactionConstants.PREPARED;
199   }
200 
201 
202   /* (non-Javadoc)
203    * @see net.jini.core.transaction.server.TransactionParticipant#commit(net.jini.core.transaction.server.TransactionManager, long)
204    */
205   @Atomic
206   public void commit(TransactionManager mgr, long id) 
207     throws UnknownTransactionException, RemoteException
208   {
209     log.debug(name + " reached COMMIT state, txn id: " + id);
210  
211 //    CreditDebit temp = pendingTxns.get(new Long(id));
212 //    if (temp != null)
213 //      doCreditDebit(temp.accNumber, temp.credit, temp.debit);
214     
215 //    pendingTxns.remove(new Long(id));
216 //    log.debug("commit(): New size of pendingTxns: " + pendingTxns.size());
217   }
218 
219 
220   /* (non-Javadoc)
221    * @see net.jini.core.transaction.server.TransactionParticipant#abort(net.jini.core.transaction.server.TransactionManager, long)
222    */
223   @Atomic 
224   public void abort(TransactionManager mgr, long id) 
225     throws UnknownTransactionException, RemoteException
226   {
227     log.debug("Bank service reached ABORT state, txn id: " + id);
228 
229 //    CreditDebit temp = pendingTxns.get(new Long(id));
230 //    log.debug(temp.toString());
231 //    pendingTxns.remove(new Long(id));
232   }
233 
234 
235   /* (non-Javadoc)
236    * @see net.jini.core.transaction.server.TransactionParticipant#prepareAndCommit(net.jini.core.transaction.server.TransactionManager, long)
237    */
238   @Atomic 
239   public int prepareAndCommit(TransactionManager mgr, long id) 
240     throws UnknownTransactionException, RemoteException
241   {
242     int result = prepare(mgr, id);
243     if (result == TransactionConstants.PREPARED) {
244       commit(mgr, id);
245       result = TransactionConstants.COMMITTED;
246     }
247     return result;    
248   }
249 
250 
251   ////////////////////////////////////////////////////////////////////////////////////////////
252   // Database methods
253   ////////////////////////////////////////////////////////////////////////////////////////////
254   
255   /**
256    * Initialize database. Get properties from objectdb.xml.
257    */
258   private void initDB() 
259   {   
260     String syspmf = System.getProperty("javax.jdo.PersistenceManagerFactoryClass",
261                                        "com.objectdb.jdo.PMF");
262 
263     String connectionURL = System.getProperty("bank2.connectionURL", "/tmp/bank2.odb");
264 
265     log.debug("syspmf: " + syspmf);
266     log.debug("connectionURL: " + connectionURL);
267     
268     Properties properties = new Properties();
269     properties.setProperty(
270         "javax.jdo.PersistenceManagerFactoryClass", syspmf);
271     properties.setProperty(
272         "javax.jdo.option.ConnectionURL", connectionURL); 
273 
274     pmf = JDOHelper.getPersistenceManagerFactory(properties, 
275                                                     JDOHelper.class.getClassLoader());          
276   
277     /** If db does not contain data, insert some default accounts and balances. */
278     Account account = null;
279     account = getAccount(pm, 111111111);
280     if (account == null) {
281       for (int i=1; i < 10; i++) {
282         insertAccount(pm, 111111111*i, 100000);
283       }
284     }
285   }
286 
287 
288   /**
289    * Insert an account object into database.
290    * 
291    * @param pm The persistence manager to use
292    * @param number The account number
293    * @param balance The balance of the account
294    */
295   private void insertAccount(PersistenceManager pm, long number, int balance) 
296   {
297     Account account = new Account(number, balance);
298     try {
299       pm = pmf.getPersistenceManager();
300       pm.currentTransaction().begin();
301       pm.makePersistent(account);
302       pm.currentTransaction().commit();
303     } catch (Exception e) {
304       pm.currentTransaction().rollback();
305       log.error(e.getMessage());
306     } finally {
307       pm.close();
308     }
309   }
310 
311 
312   /**
313    * Update an account object.
314    * 
315    * @param pm The persistence manager to use
316    * @param number The account number to update
317    * @param balance The new balance of the account
318    */
319   private boolean updateAccount(PersistenceManager pm, long number, int balance)
320   {
321     Account account = null;
322     boolean update = true;
323     
324     try {
325       pm = pmf.getPersistenceManager();
326       pm.currentTransaction().begin();
327       Query query = pm.newQuery(Account.class, "number == " + number);
328       
329       Collection result = (Collection) query.execute();
330       
331       account = (Account) result.iterator().next();
332       
333       if (account == null) {
334         update = false;
335         log.debug("Did not find a matching instance");
336         pm.currentTransaction().rollback();  
337       }
338       
339       account.setBalance(balance);
340       pm.currentTransaction().commit(); 
341     } catch (Exception e) {
342       pm.currentTransaction().rollback();
343       log.error(e.getMessage());
344     } finally {  
345         pm.close();        
346       }
347       return update;
348     }
349 
350 
351   /**
352    * Returns an account object with given account number.
353    * 
354    * @param pm The persistence manager to use
355    * @param number Account number of account to return
356    * @return The account object
357    */
358   private Account getAccount(PersistenceManager pm, long number)
359   {
360     Account account = null;
361     
362     try {
363       pm = pmf.getPersistenceManager();
364   
365       Query query = pm.newQuery(Account.class, "number == " + number);
366   
367       Collection<Account> result = (Collection) query.execute();
368       
369       account = (Account) result.iterator().next();
370       
371       if (account == null) {
372         log.debug("Did not find a matching instance");
373         pm.currentTransaction().rollback();  
374       }
375     } catch (Exception e) {
376       log.error(e.getMessage());
377     } finally {
378       pm.close();
379     }
380     return account;
381   }
382 
383 
384   ////////////////////////////////////////////////////////////////////////////////////////////
385   // Inner classes
386   ////////////////////////////////////////////////////////////////////////////////////////////  
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 = 6912651262566516037L;
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 ReplicatedBankServer2