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