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 NonReplicatedBankServer 
64    implements Bank, TransactionParticipant, ServiceIDListener
65  {
66  
67    ////////////////////////////////////////////////////////////////////////////////////////////
68    // Logger
69    ////////////////////////////////////////////////////////////////////////////////////////////
70  
71    private static final Logger log = Logger.getLogger(NonReplicatedBankServer.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 NonReplicatedBankServer(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", "NOKAS/RobberyBank");
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     String[] groups = new String[] {System.getProperty("bank.lookup.group", "jgroup.sf.net")};
128     LookupDiscovery ld = new LookupDiscovery(groups);
129     new JoinManager(proxy, entries, this, ld, null);
130     
131     initDB();  
132    }
133 
134 
135   ////////////////////////////////////////////////////////////////////////////////////////////
136   // Methods from Bank
137   ////////////////////////////////////////////////////////////////////////////////////////////
138    
139   /* (non-Javadoc)
140    * @see jgroup.test.jini.txn.Bank#getBalance(long)
141    */
142   public Integer getBalance(long accNumber) 
143     throws RemoteException
144   {
145     Integer balance = accounts.get(new Long(accNumber));
146     if (balance == null) balance = new Integer(0); 
147     return balance;
148   }
149 
150 
151   /* (non-Javadoc)
152    * @see jgroup.test.jini.txn.Bank#withdraw(long, int, net.jini.core.transaction.Transaction)
153    */
154   public void withdraw(long accountNumber, int amount, long transactionID)
155     throws RemoteException
156   {
157 //    pendingTxns.put(new Long(transactionID), new CreditDebit(accountNumber, 0, amount));
158     log.debug("Withdraw=" + amount + ",Account=" + accountNumber + ",tid=" + transactionID);
159   }
160 
161 
162   /* (non-Javadoc)
163    * @see jgroup.test.jini.txn.Bank#deposit(long, int, net.jini.core.transaction.Transaction)
164    */
165   public void deposit(long accountNumber, int amount, long transactionID)
166     throws RemoteException
167   {
168 //    pendingTxns.put(new Long(transactionID), new CreditDebit(accountNumber, amount, 0));
169     log.debug("Deposit=" + amount + ",Account=" + accountNumber + ",tid=" + transactionID);
170   }
171 
172 
173   /* (non-Javadoc)
174    * @see jgroup.test.jini.txn.Bank#getName()
175    */
176   public String getName()
177     throws RemoteException
178   {
179     return name;
180   }
181 
182 
183   /**
184    * Do the credit/debit operation.
185    * 
186    * @param accNumber Account number to credit/debit
187    * @param credit Amount to credit
188    * @param debit Amount to debit
189    */
190   private void doCreditDebit(long accNumber, int credit, int debit)
191   { 
192 //    log.debug("credit = " + credit + " debit = " + debit);
193     Account account = getAccount(pm, accNumber);
194 //    log.debug("Balance BEFORE commit for acc.nr " + accNumber + " is " + account.getBalance());
195     int newBalance = account.getBalance();
196     newBalance += credit;
197     newBalance -= debit;
198     updateAccount(pm, accNumber, newBalance);    
199 //    log.debug("Balance AFTER commit for acc.nr " + accNumber + " is " + account.getBalance());
200   }
201 
202 
203   ////////////////////////////////////////////////////////////////////////////////////////////
204   // Methods from TransactionParticipant
205   ////////////////////////////////////////////////////////////////////////////////////////////
206 
207   /* (non-Javadoc)
208    * @see net.jini.core.transaction.server.TransactionParticipant#prepare(net.jini.core.transaction.server.TransactionManager, long)
209    */
210   public int prepare(TransactionManager mgr, long id) 
211     throws UnknownTransactionException, RemoteException
212   {
213     log.debug(name + " reached PREPARE state, txn id:" + id);
214     return TransactionConstants.PREPARED;
215   }
216 
217 
218   /* (non-Javadoc)
219    * @see net.jini.core.transaction.server.TransactionParticipant#commit(net.jini.core.transaction.server.TransactionManager, long)
220    */
221   public void commit(TransactionManager mgr, long id) 
222     throws UnknownTransactionException, RemoteException
223   {
224     log.debug(name + " 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 //    
230 //    pendingTxns.remove(new Long(id));
231 //    log.debug("commit(): New size of pendingTxns: " + pendingTxns.size());
232   }
233 
234 
235   /* (non-Javadoc)
236    * @see net.jini.core.transaction.server.TransactionParticipant#abort(net.jini.core.transaction.server.TransactionManager, long)
237    */
238   public void abort(TransactionManager mgr, long id) 
239     throws UnknownTransactionException, RemoteException
240   {
241     log.debug(name + " reached ABORT state for txn id: " + id);
242     
243 //    CreditDebit temp = pendingTxns.get(new Long(id));
244 //    log.debug(temp.toString());
245 //    pendingTxns.remove(new Long(id));
246   }
247 
248 
249   /* (non-Javadoc)
250    * @see net.jini.core.transaction.server.TransactionParticipant#prepareAndCommit(net.jini.core.transaction.server.TransactionManager, long)
251    */
252   public int prepareAndCommit(TransactionManager mgr, long id) 
253     throws UnknownTransactionException, RemoteException
254   {
255     int result = prepare(mgr, id);
256     if (result == TransactionConstants.PREPARED) {
257       commit(mgr, id);
258       result = TransactionConstants.COMMITTED;
259     }
260     return result;    
261   }
262 
263 
264   ////////////////////////////////////////////////////////////////////////////////////////////
265   // Database methods
266   ////////////////////////////////////////////////////////////////////////////////////////////
267   
268   /**
269    * Initialize database. Get properties from objectdb.xml.
270    */
271   private void initDB() 
272   { 
273     String syspmf = System.getProperty("javax.jdo.PersistenceManagerFactoryClass", 
274                                         "com.objectdb.jdo.PMF");
275     String connectionURL = System.getProperty("bank.connectionURL", "/tmp/bank.odb");
276     
277     log.debug("syspmf: " + syspmf);
278     log.debug("connectionURL: " + connectionURL);
279     
280     Properties properties = new Properties();
281     properties.setProperty(
282         "javax.jdo.PersistenceManagerFactoryClass", syspmf);
283     properties.setProperty(
284         "javax.jdo.option.ConnectionURL", connectionURL); 
285 
286     pmf = JDOHelper.getPersistenceManagerFactory(properties, 
287                                                     JDOHelper.class.getClassLoader());          
288   
289     /** If db does not contain data, insert some default accounts and balances. */
290     Account account = null;
291     account = getAccount(pm, 111111111);
292     if (account == null) {
293       for (int i=1; i < 10; i++) {
294         insertAccount(pm, 111111111*i, 100000);
295       }
296     }
297   }
298 
299 
300   /**
301    * Insert an account object into database.
302    * 
303    * @param pm The persistence manager to use
304    * @param number The account number
305    * @param balance The balance of the account
306    */
307   private void insertAccount(PersistenceManager pm, long number, int balance) 
308   {
309     Account account = new Account(number, balance);
310     try {
311       pm = pmf.getPersistenceManager();
312       pm.currentTransaction().begin();
313       pm.makePersistent(account);
314       pm.currentTransaction().commit();
315     } catch (Exception e) {
316       pm.currentTransaction().rollback();
317       log.error(e.getMessage());
318     } finally {
319       pm.close();
320     }
321   }
322 
323 
324   /**
325    * Update an account object.
326    * 
327    * @param pm The persistence manager to use
328    * @param number The account number to update
329    * @param balance The new balance of the account
330    */
331   private boolean updateAccount(PersistenceManager pm, long number, int balance)
332   {
333     Account account = null;
334     boolean update = true;
335     
336     try {
337       pm = pmf.getPersistenceManager();
338       pm.currentTransaction().begin();
339       Query query = pm.newQuery(Account.class, "number == " + number);
340       
341       Collection result = (Collection) query.execute();
342       
343       account = (Account) result.iterator().next();
344       
345       if (account == null) {
346         update = false;
347         log.debug("Did not find a matching instance");
348         pm.currentTransaction().rollback();  
349       }
350       
351       account.setBalance(balance);
352       pm.currentTransaction().commit(); 
353     } catch (Exception e) {
354       pm.currentTransaction().rollback();
355       log.error(e.getMessage());
356     } finally {  
357         pm.close();        
358       }
359       return update;
360     }
361 
362 
363   /**
364    * Returns an account object with given account number.
365    * 
366    * @param pm The persistence manager to use
367    * @param number Account number of account to return
368    * @return The account object
369    */
370   private Account getAccount(PersistenceManager pm, long number)
371   {
372     Account account = null;
373     
374     try {
375       pm = pmf.getPersistenceManager();
376   
377       Query query = pm.newQuery(Account.class, "number == " + number);
378   
379       Collection result = (Collection) query.execute();
380       
381       account = (Account) result.iterator().next();
382       
383       if (account == null) {
384         log.debug("Did not find a matching instance");
385         pm.currentTransaction().rollback();  
386       }
387     } catch (Exception e) {
388       log.error(e.getMessage());
389     } finally {
390       pm.close();
391     }
392     return account;
393   }
394 
395 
396   ////////////////////////////////////////////////////////////////////////////////////////////
397   // Inner classes
398   ////////////////////////////////////////////////////////////////////////////////////////////  
399   
400   /**
401    * Class that holds a temporary credit/debit operation, before doing a commit/abort operation.
402    */
403   public class CreditDebit implements Serializable
404   {
405     private static final long serialVersionUID = -923925461553488080L;
406     int credit;
407     int debit;
408     long accNumber;
409     
410     CreditDebit(long accNumber, int credit, int debit) 
411     {
412      this.accNumber = accNumber;
413      this.credit = credit;
414       this.debit = debit;
415     }
416     
417     public String toString() 
418     {
419       String temp = "CreditDebit { \n";
420       temp += "accNumber=" + accNumber + "\n";
421       temp += "credit=" + credit + "\n";
422       temp += "debit=" + debit + "\n";
423       temp += "} \n";
424       return temp;
425     }
426   }
427 
428   
429   ////////////////////////////////////////////////////////////////////////////////////////////
430   // Methods from ServiceIDListener
431   ////////////////////////////////////////////////////////////////////////////////////////////  
432 
433   /* (non-Javadoc)
434    * @see net.jini.lookup.ServiceIDListener#serviceIDNotify(net.jini.core.lookup.ServiceID)
435    */
436   public void serviceIDNotify(ServiceID serviceID) 
437   {
438     log.debug("Service ID: " + serviceID);
439   }
440   
441 } // END NonReplicatedBankServer
442