View Javadoc

1   /*
2    * 
3    * Copyright 2005 Sun Microsystems, Inc.
4    * 
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    * 
9    *  http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   * 
17   * Note: This is a modified version of TxnManagerTransactionImpl to 
18   *       support replicated transactions.
19   * 
20   */
21  
22  package com.sun.jini.mahalo;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.rmi.MarshalledObject;
27  import java.rmi.RemoteException;
28  import java.rmi.activation.Activatable;
29  import java.rmi.activation.ActivationException;
30  import java.rmi.activation.ActivationGroup;
31  import java.rmi.activation.ActivationID;
32  import java.rmi.activation.ActivationSystem;
33  import java.security.PrivilegedActionException;
34  import java.security.PrivilegedExceptionAction;
35  import java.security.SecureRandom;
36  import java.util.Arrays;
37  import java.util.Collections;
38  import java.util.HashMap;
39  import java.util.Iterator;
40  import java.util.Map;
41  import java.util.Vector;
42  import java.util.logging.Level;
43  import java.util.logging.Logger;
44  
45  import javax.security.auth.Subject;
46  import javax.security.auth.login.LoginContext;
47  import javax.security.auth.login.LoginException;
48  
49  import net.jini.activation.ActivationExporter;
50  import net.jini.config.Configuration;
51  import net.jini.config.ConfigurationProvider;
52  import net.jini.core.constraint.RemoteMethodControl;
53  import net.jini.core.discovery.LookupLocator;
54  import net.jini.core.entry.Entry;
55  import net.jini.core.lease.Lease;
56  import net.jini.core.lease.LeaseDeniedException;
57  import net.jini.core.lease.UnknownLeaseException;
58  import net.jini.core.lookup.ServiceID;
59  import net.jini.core.transaction.CannotAbortException;
60  import net.jini.core.transaction.CannotCommitException;
61  import net.jini.core.transaction.CannotJoinException;
62  import net.jini.core.transaction.TimeoutExpiredException;
63  import net.jini.core.transaction.Transaction;
64  import net.jini.core.transaction.TransactionException;
65  import net.jini.core.transaction.UnknownTransactionException;
66  import net.jini.core.transaction.server.CrashCountException;
67  import net.jini.core.transaction.server.ServerTransaction;
68  import net.jini.core.transaction.server.TransactionManager;
69  import net.jini.core.transaction.server.TransactionParticipant;
70  import net.jini.export.Exporter;
71  import net.jini.export.ProxyAccessor;
72  import net.jini.id.Uuid;
73  import net.jini.id.UuidFactory;
74  import net.jini.jeri.BasicILFactory;
75  import net.jini.jeri.BasicJeriExporter;
76  import net.jini.jeri.tcp.TcpServerEndpoint;
77  import net.jini.lookup.entry.ServiceInfo;
78  import net.jini.security.BasicProxyPreparer;
79  import net.jini.security.ProxyPreparer;
80  import net.jini.security.TrustVerifier;
81  import net.jini.security.proxytrust.ServerProxyTrust;
82  
83  import com.sun.jini.config.Config;
84  import com.sun.jini.landlord.FixedLeasePeriodPolicy;
85  import com.sun.jini.landlord.Landlord;
86  import com.sun.jini.landlord.LandlordUtil;
87  import com.sun.jini.landlord.LeaseFactory;
88  import com.sun.jini.landlord.LeasePeriodPolicy;
89  import com.sun.jini.landlord.LeasedResource;
90  import com.sun.jini.landlord.LocalLandlord;
91  import com.sun.jini.landlord.LeasePeriodPolicy.Result;
92  import com.sun.jini.logging.Levels;
93  import com.sun.jini.mahalo.log.ClientLog;
94  import com.sun.jini.mahalo.log.LogException;
95  import com.sun.jini.mahalo.log.LogManager;
96  import com.sun.jini.mahalo.log.LogRecord;
97  import com.sun.jini.mahalo.log.LogRecovery;
98  import com.sun.jini.mahalo.log.MultiLogManager;
99  import com.sun.jini.mahalo.log.MultiLogManagerAdmin;
100 import com.sun.jini.start.LifeCycle;
101 import com.sun.jini.thread.InterruptedStatusThread;
102 import com.sun.jini.thread.ReadyState;
103 import com.sun.jini.thread.TaskManager;
104 import com.sun.jini.thread.WakeupManager;
105 
106 public class GroupTxnManagerImpl
107   implements TxnManager, LeaseExpirationMgr.Expirer,
108     LogRecovery, TxnSettler, com.sun.jini.constants.TimeConstants, LocalLandlord,
109     ServerProxyTrust, ProxyAccessor
110 {
111   /** Logger for (successful) service startup message */
112   static final Logger startupLogger = 
113       Logger.getLogger(TxnManager.MAHALO + ".startup");
114   
115   /** Logger for service re/initialization related messages */
116   static final Logger initLogger = 
117       Logger.getLogger(TxnManager.MAHALO + ".init");
118     
119   /** Logger for service destruction related messages */
120   static final Logger destroyLogger = 
121       Logger.getLogger(TxnManager.MAHALO + ".destroy");
122 
123   /** Logger for service operation messages */
124   static final Logger operationsLogger = 
125       Logger.getLogger(TxnManager.MAHALO + ".operations");
126 
127   /** 
128    * Logger for transaction related messages 
129    * (creation, destruction, transition, etc.) 
130    */
131   static final Logger transactionsLogger = 
132       Logger.getLogger(TxnManager.MAHALO + ".transactions");
133 
134   /** Logger for transaction participant related messages */
135   static final Logger participantLogger = 
136       Logger.getLogger(TxnManager.MAHALO + ".participant");
137 
138   /** Logger for transaction persistence related messages */
139   static final Logger persistenceLogger = 
140       Logger.getLogger(TxnManager.MAHALO + ".persistence");
141 
142   /**
143    * @serial
144    */
145   private LogManager logmgr;
146 
147   /* Default tuning parameters for thread pool */
148   /* Retrieve values from properties.          */
149 
150   private transient int settlerthreads = 150;
151   private transient long settlertimeout = 1000 * 15;
152   private transient float settlerload = 1.0f;
153 
154 
155   private transient int taskthreads = 50;
156   private transient long tasktimeout = 1000 * 15;
157   private transient float taskload = 3.0f;
158 
159 
160   /* Its important here to schedule SettlerTasks on a */
161   /* different TaskManager from what is given to      */
162   /* TxnManagerTransaction objects.  Tasks on a given */
163   /* TaskManager which create Tasks cannot be on the  */
164   /* same TaskManager as their child Tasks.     */
165 
166   private transient TaskManager settlerpool;
167   /** wakeup manager for <code>SettlerTask</code> */
168   private WakeupManager settlerWakeupMgr;
169 
170   private transient TaskManager taskpool;
171   /** wakeup manager for <code>ParticipantTask</code> */
172   private WakeupManager taskWakeupMgr;
173 
174   /*
175    * Map of transaction ids are their associated, internal 
176    * transaction representations 
177    */
178   protected transient Map<Long,GroupTxnManagerTransaction>       txns;
179 
180   private transient Vector      unsettledtxns;
181   private transient InterruptedStatusThread settleThread;
182 
183   /**
184    * @serial
185    */
186   private String persistenceDirectory = null;
187 
188   /**
189    * @serial
190    */
191   private ActivationID activationID;
192   
193   /** Whether the activation ID has been prepared */
194   private boolean activationPrepared;
195 
196   /** The activation system, prepared */
197   private ActivationSystem activationSystem;
198 
199   /** Proxy preparer for listeners */
200   private ProxyPreparer participantPreparer;
201 
202   /** The exporter for exporting and unexporting */
203   protected Exporter exporter;
204 
205   /** The login context, for logging out */
206   protected LoginContext loginContext;
207 
208   /** The generator for our IDs. */
209   private static transient SecureRandom idGen = new SecureRandom();
210 
211   /** The buffer for generating IDs. */
212   private static transient final byte[] idGenBuf = new byte[8];
213 
214 
215   /** 
216    * <code>LeaseExpirationMgr</code> used by our <code>LeasePolicy</code>.
217    */
218   private LeaseExpirationMgr expMgr;
219 
220   /**
221    * @serial
222    */
223   private /*final*/ LeasePeriodPolicy txnLeasePeriodPolicy = null;
224   
225   /** <code>LandLordLeaseFactory</code> we use to create leases */
226   private LeaseFactory leaseFactory = null;
227 
228   /**
229    * @serial
230    */
231   private JoinStateManager joinStateManager;
232 
233   /**
234    * The <code>Uuid</code> for this service. Used in the
235    * <code>TxnMgrProxy</code> and <code>TxnMgrAdminProxy</code> to
236    * implement reference equality. We also derive our
237    * <code>ServiceID</code> from it.
238    */
239   private Uuid topUuid = null;
240 
241   /** The outter proxy of this server */
242   private TxnMgrProxy txnMgrProxy;      
243 
244   /** The admin proxy of this server */
245   private TxnMgrAdminProxy txnMgrAdminProxy;        
246 
247   /**
248    * Cache of our inner proxy.
249    */
250   private TxnManager serverStub = null;
251 
252   /**
253    * Cache of our <code>LifeCycle</code> object
254    */
255   private LifeCycle lifeCycle = null;
256   
257   /** 
258    * Object used to prevent access to this service during the service's
259    *  initialization or shutdown processing.
260    */
261   private final ReadyState readyState = new ReadyState();
262 
263   /**
264    * <code>boolean</code> flag used to determine persistence support.
265    * Defaulted to true, and overridden in the constructor overload that takes
266    * a <code>boolean</code> argument.
267    */
268   private boolean persistent = true;
269 
270   /**
271    * Constructs a non-activatable transaction manager.
272    *
273    * @param args Service configuration options
274    *
275    * @param lc <code>LifeCycle</code> reference used for callback
276    */
277   GroupTxnManagerImpl(String[] args, LifeCycle lc, boolean persistent)
278     throws Exception
279   {
280       if (operationsLogger.isLoggable(Level.FINER)) {
281           operationsLogger.entering(
282         GroupTxnManagerImpl.class.getName(), "GroupTxnManagerImpl",
283             new Object[] {
284             Arrays.asList(args), lc, Boolean.valueOf(persistent)});
285     }
286     lifeCycle = lc; 
287     this.persistent = persistent;
288     try {
289           init(args);
290     } catch (Throwable e) {
291           cleanup();
292           initFailed(e); 
293     }
294       if (operationsLogger.isLoggable(Level.FINER)) {
295           operationsLogger.exiting(
296         GroupTxnManagerImpl.class.getName(), "GroupTxnManagerImpl");
297     }
298   }
299   
300   /**
301    * Constructs an activatable transaction manager.
302    *
303    * @param activationID activation ID passed in by the activation daemon.
304    *
305    * @param data state data needed to re-activate a transaction manager.
306    */
307   GroupTxnManagerImpl(ActivationID activationID, MarshalledObject data)
308     throws Exception
309   {
310       if (operationsLogger.isLoggable(Level.FINER)) {
311           operationsLogger.entering(
312         GroupTxnManagerImpl.class.getName(), "GroupTxnManagerImpl",
313             new Object[] {activationID, data} );
314     }
315     this.activationID = activationID;
316       try {
317           // Initialize state
318           init((String[])data.get());
319       } catch (Throwable e) {
320           cleanup();
321         initFailed(e); 
322       }
323       if (operationsLogger.isLoggable(Level.FINER)) {
324           operationsLogger.exiting(
325         GroupTxnManagerImpl.class.getName(), "GroupTxnManagerImpl");
326     }
327   }
328   
329   /** Initialization common to both activatable and transient instances. */
330   private void init(String[] configArgs)
331       throws Exception
332   {
333       if (operationsLogger.isLoggable(Level.FINER)) {
334           operationsLogger.entering(GroupTxnManagerImpl.class.getName(), "init",
335             (Object[])configArgs );
336     }
337       final Configuration config =
338           ConfigurationProvider.getInstance(
339         configArgs, getClass().getClassLoader());
340       loginContext = (LoginContext) config.getEntry(
341           TxnManager.MAHALO, "loginContext", LoginContext.class, null);
342       if (loginContext != null) {
343           doInitWithLogin(config, loginContext);
344       } else {
345           doInit(config);
346       }
347       if (operationsLogger.isLoggable(Level.FINER)) {
348           operationsLogger.exiting(
349         GroupTxnManagerImpl.class.getName(), "init");
350     }
351   }
352   
353   private void doInitWithLogin(final Configuration config,
354       LoginContext loginContext) throws Exception
355   {
356       if (operationsLogger.isLoggable(Level.FINER)) {
357           operationsLogger.entering(GroupTxnManagerImpl.class.getName(), 
358             "doInitWithLogin",
359             new Object[] { config, loginContext } );
360     }
361       loginContext.login();
362       try {
363           Subject.doAsPrivileged(
364               loginContext.getSubject(),
365               new PrivilegedExceptionAction() {
366                   public Object run() throws Exception {
367                       doInit(config);
368                       return null;
369                   }
370               },
371               null);
372       } catch (PrivilegedActionException e) {
373 //TODO - move to end of initFailed() so that shutdown still occurs under login  
374           try {
375               loginContext.logout();
376           } catch (LoginException le) {
377               if( initLogger.isLoggable(Levels.HANDLED) ) {
378             initLogger.log(Levels.HANDLED, "Trouble logging out", le);
379         }
380           }
381           throw e.getException();
382       }
383       if (operationsLogger.isLoggable(Level.FINER)) {
384           operationsLogger.exiting(GroupTxnManagerImpl.class.getName(), 
385             "doInitWithLogin");
386     }
387 
388   }
389 
390   private void doInit(Configuration config) throws Exception {
391       if (operationsLogger.isLoggable(Level.FINER)) {
392           operationsLogger.entering(
393         GroupTxnManagerImpl.class.getName(), "doInit", config);
394     }
395       // Get activatable settings, if activated
396       if (activationID != null) {
397           ProxyPreparer activationSystemPreparer =
398               (ProxyPreparer) Config.getNonNullEntry(config,
399                   TxnManager.MAHALO, "activationSystemPreparer",
400                   ProxyPreparer.class, new BasicProxyPreparer());
401           if(initLogger.isLoggable(Level.CONFIG)) {
402         initLogger.log(Level.CONFIG, "activationSystemPreparer: {0}", 
403                 activationSystemPreparer);      
404         }
405           activationSystem =
406               (ActivationSystem) activationSystemPreparer.prepareProxy(
407                   ActivationGroup.getSystem());
408           if(initLogger.isLoggable(Level.CONFIG)) {
409         initLogger.log(Level.CONFIG, "Prepared activation system is: {0}",
410                   activationSystem);
411           }
412           ProxyPreparer activationIdPreparer =
413               (ProxyPreparer) Config.getNonNullEntry(config,
414                   TxnManager.MAHALO, "activationIdPreparer",
415                   ProxyPreparer.class, new BasicProxyPreparer());
416           if(initLogger.isLoggable(Level.CONFIG)) {
417         initLogger.log(Level.CONFIG, "activationIdPreparer: {0}", 
418                 activationIdPreparer);      
419           }
420         activationID = (ActivationID) activationIdPreparer.prepareProxy(
421               activationID);
422           if(initLogger.isLoggable(Level.CONFIG)) {
423         initLogger.log(Level.CONFIG, "Prepared activationID is: {0}",
424                   activationID);
425           }
426         activationPrepared = true;
427           exporter = (Exporter)Config.getNonNullEntry(config,
428             TxnManager.MAHALO, "serverExporter", Exporter.class,
429               new ActivationExporter(
430             activationID,
431                   new BasicJeriExporter(
432             TcpServerEndpoint.getInstance(0), 
433             new BasicILFactory(), false, true)),
434             activationID);
435           if(initLogger.isLoggable(Level.CONFIG)) {
436         initLogger.log(Level.CONFIG, 
437                 "Activatable service exporter is: {0}", exporter);
438         }
439       } else {
440           exporter = (Exporter) Config.getNonNullEntry(config,
441               TxnManager.MAHALO, "serverExporter", Exporter.class,
442               new BasicJeriExporter( 
443             TcpServerEndpoint.getInstance(0), 
444             new BasicILFactory(), false, true));
445           if(initLogger.isLoggable(Level.CONFIG)) {
446         initLogger.log(Level.CONFIG, 
447                 "Non-activatable service exporter is: {0}", exporter);
448           }
449     }
450     
451     ProxyPreparer recoveredParticipantPreparer = 
452         (ProxyPreparer)Config.getNonNullEntry(config,
453               TxnManager.MAHALO, "recoveredParticipantPreparer", 
454         ProxyPreparer.class, new BasicProxyPreparer());
455       if(initLogger.isLoggable(Level.CONFIG)) {
456         initLogger.log(Level.CONFIG, "Recovered participant preparer is: {0}",
457               recoveredParticipantPreparer);
458       }
459     participantPreparer = (ProxyPreparer)Config.getNonNullEntry(config,
460           TxnManager.MAHALO, "participantPreparer", ProxyPreparer.class,
461           new BasicProxyPreparer());
462       if(initLogger.isLoggable(Level.CONFIG)) {
463         initLogger.log(Level.CONFIG, "Participant preparer is: {0}",
464               participantPreparer);
465       }
466     // Create lease policy -- used by recovery logic, below??
467       txnLeasePeriodPolicy = (LeasePeriodPolicy)Config.getNonNullEntry(
468           config, TxnManager.MAHALO, "leasePeriodPolicy", 
469         LeasePeriodPolicy.class,
470           new FixedLeasePeriodPolicy(3 * HOURS, 1 * HOURS));
471       if(initLogger.isLoggable(Level.CONFIG)) {
472         initLogger.log(Level.CONFIG, "leasePeriodPolicy is: {0}",
473               txnLeasePeriodPolicy);
474     }    
475     
476     if (persistent) {
477           persistenceDirectory =
478               (String)Config.getNonNullEntry(config,
479                   TxnManager.MAHALO, "persistenceDirectory", String.class);
480           if(initLogger.isLoggable(Level.CONFIG)) {
481             initLogger.log(Level.CONFIG, "Persistence directory is: {0}",
482                   persistenceDirectory);
483           }
484     } else { // just for insurance
485         persistenceDirectory = null;
486     }
487     
488     if(initLogger.isLoggable(Level.FINEST)) {
489         initLogger.log(Level.FINEST, "Creating JoinStateManager");
490       }
491     // Note: null persistenceDirectory means no persistence
492     joinStateManager = new JoinStateManager(persistenceDirectory);
493     if(initLogger.isLoggable(Level.FINEST)) {
494         initLogger.log(Level.FINEST, "Recovering join state ...");
495     }
496     joinStateManager.recover();
497     
498     // ServiceUuid will be null first time up.
499     if (joinStateManager.getServiceUuid() == null) {
500         if(initLogger.isLoggable(Level.FINEST)) {
501             initLogger.log(Level.FINEST, "Generating service Uuid");
502         }
503         topUuid = UuidFactory.generate();
504         // Actual snapshot deferred until JSM is started, below
505         joinStateManager.setServiceUuid(topUuid);
506     } else { // get recovered value for serviceUuid
507         if(initLogger.isLoggable(Level.FINEST)) {
508             initLogger.log(Level.FINEST, "Recovering service Uuid");
509         }
510         topUuid = joinStateManager.getServiceUuid();
511     }
512       if(initLogger.isLoggable(Level.FINEST)) {
513         initLogger.log(Level.FINEST, "Uuid is: {0}", topUuid);
514     }
515     
516     if (persistent) {
517           // Check persistence path for validity, and create if necessary
518           com.sun.jini.system.FileSystem.ensureDir(persistenceDirectory);
519     }
520     
521     if(initLogger.isLoggable(Level.FINEST)) {
522         initLogger.log(Level.FINEST, "Exporting server");
523     }
524     serverStub = (TxnManager)exporter.export(this);
525       if(initLogger.isLoggable(Level.FINEST)) {
526         initLogger.log(Level.FINEST, "Server stub: {0}", serverStub);
527     }
528     // Create the proxy that will be registered in the lookup service
529       txnMgrProxy = 
530         TxnMgrProxy.create(serverStub, topUuid);
531       if(initLogger.isLoggable(Level.FINEST)) {
532         initLogger.log(Level.FINEST, "Service proxy is: {0}", 
533             txnMgrProxy);       
534       }
535     // Create the admin proxy for this service
536       txnMgrAdminProxy = 
537         TxnMgrAdminProxy.create(serverStub, topUuid);
538       if(initLogger.isLoggable(Level.FINEST)) {
539         initLogger.log(Level.FINEST, "Service admin proxy is: {0}", 
540             txnMgrAdminProxy);      
541     }
542     if(initLogger.isLoggable(Level.FINEST)) {
543         initLogger.log(Level.FINEST, "Setting up data structures");
544       }
545     txns = Collections.synchronizedMap(new HashMap<Long, GroupTxnManagerTransaction>());
546       
547       // Used by log recovery logic         
548       settlerWakeupMgr =
549           new WakeupManager(new WakeupManager.ThreadDesc(null, true));
550     taskWakeupMgr =
551           new WakeupManager(new WakeupManager.ThreadDesc(null, true));
552     
553       settlerpool =
554           (TaskManager) Config.getNonNullEntry(
555               config, TxnManager.MAHALO, "settlerPool", TaskManager.class,
556               new TaskManager(settlerthreads, settlertimeout,
557                               settlerload));
558       taskpool =
559           (TaskManager) Config.getNonNullEntry(
560               config, TxnManager.MAHALO, "taskPool", TaskManager.class,
561               new TaskManager(taskthreads, tasktimeout,
562                               taskload));  
563       
564       unsettledtxns = new Vector();
565     
566       // Create leaseFactory
567       leaseFactory = new LeaseFactory(serverStub, topUuid);
568     
569       // Create LeaseExpirationMgr
570       expMgr = new LeaseExpirationMgr(this);
571 
572     if(initLogger.isLoggable(Level.FINEST)) {
573         initLogger.log(Level.FINEST, "Setting up log manager");
574       }
575     if (persistent) {
576         logmgr = new MultiLogManager(this, persistenceDirectory);
577     } else {
578         logmgr = new MultiLogManager();
579     }
580 
581       try {
582         if(initLogger.isLoggable(Level.FINEST)) {
583             initLogger.log(Level.FINEST, "Recovering state");
584           }
585         logmgr.recover();
586         
587         // Restore transient state of recovered transactions
588         Iterator iter = txns.values().iterator();
589           TxnManagerTransaction txn;
590           while(iter.hasNext()) {
591             txn = (TxnManagerTransaction)iter.next();
592               if(initLogger.isLoggable(Level.FINEST)) {
593                 initLogger.log(Level.FINEST, 
594                 "Restoring transient state for txn id: {0}", 
595                     new Long(((ServerTransaction)txn.getTransaction()).id));        
596         }
597         try {
598             txn.restoreTransientState(recoveredParticipantPreparer);
599         } catch (RemoteException re) {
600                   if (persistenceLogger.isLoggable(Level.WARNING)) {
601                       persistenceLogger.log(Level.WARNING,
602                     "Cannot restore the TransactionParticipant", re);
603             }
604 //TODO - what should happen when participant preparation fails?     
605         }
606         }
607 
608         if(initLogger.isLoggable(Level.FINEST)) {
609             initLogger.log(Level.FINEST, "Settling incomplete transactions");
610         }
611           settleThread = new InterruptedStatusThread("settleThread") {
612               public void run() {
613                   try {
614                 settleTxns();
615             } catch (InterruptedException ie) {
616                       if (transactionsLogger.isLoggable(Level.FINEST)) {
617                           transactionsLogger.log(Level.FINEST,
618                               "settleThread interrupted -- exiting");
619                       }
620                 return;
621             }
622               };
623           };
624           settleThread.start();
625       } catch (LogException le) {
626           RemoteException re =  
627             new RemoteException("Problem recovering state");
628         initLogger.throwing(GroupTxnManagerImpl.class.getName(), "doInit", re);
629         throw re;
630       }
631 
632     /*
633      * With SecureRandom, the first ID requires generation of a
634      * secure seed, which can take several seconds.  We do it here
635      * so it doesn't affect the first call's time.  (I tried doing
636      * this in a separate thread so some of the startup would occur
637      * during the roundtrip back the client, but it didn't help
638      * much and this is simpler.)
639      */
640     nextID();
641     
642     
643     /*
644      * Create the object that manages and persists our join state
645      */
646     if(initLogger.isLoggable(Level.FINEST)) {
647         initLogger.log(Level.FINEST, "Starting JoinStateManager");
648       }
649     // Starting causes snapshot to occur
650     joinStateManager.startManager(config, txnMgrProxy, 
651         new ServiceID(topUuid.getMostSignificantBits(),
652                   topUuid.getLeastSignificantBits()),
653           attributesFor());
654         
655       if (startupLogger.isLoggable(Level.INFO)) {
656           startupLogger.log
657                  (Level.INFO, "Mahalo started: {0}", this);
658       }
659       readyState.ready();
660     
661       if (operationsLogger.isLoggable(Level.FINER)) {
662           operationsLogger.exiting(
663         GroupTxnManagerImpl.class.getName(), "doInit");
664     }
665   }
666 
667 
668   //TransactionManager interface method
669 
670   public TransactionManager.Created create(long lease)
671       throws LeaseDeniedException
672   {
673       if (operationsLogger.isLoggable(Level.FINER)) {
674           operationsLogger.entering(
675         GroupTxnManagerImpl.class.getName(), "create", 
676             new Long(lease));
677     }
678       readyState.check();
679   
680     GroupTxnManagerTransaction txntr = null;
681 
682     long tid = nextID();
683       Uuid uuid = createLeaseUuid(tid);
684 
685       if (transactionsLogger.isLoggable(Level.FINEST)) {
686           transactionsLogger.log(Level.FINEST, 
687             "Transaction ID is: {0}", new Long(tid));
688     }
689 
690       txntr = new GroupTxnManagerTransaction(
691         txnMgrProxy, logmgr, tid, taskpool, 
692         taskWakeupMgr, this, uuid);
693     Lease txnmgrlease = null;
694     try {
695           Result r = txnLeasePeriodPolicy.grant(txntr, lease);
696           txntr.setExpiration(r.expiration);
697           txnmgrlease = 
698             leaseFactory.newLease(
699             uuid,
700                 r.expiration);
701           expMgr.register(txntr);       
702     } catch (LeaseDeniedException lde) {
703           // Should never happen in our implementation.
704           throw new AssertionError("Transaction lease was denied" + lde);
705       }
706 
707       if (transactionsLogger.isLoggable(Level.FINEST)) {
708           transactionsLogger.log(Level.FINEST, 
709             "Created new TxnManagerTransaction ID is: {0}", new Long(tid));
710     }
711 
712     Transaction tr = txntr.getTransaction();
713     ServerTransaction str = null;
714 
715       try {
716         str = serverTransaction(tr);
717         txns.put(new Long(str.id), txntr);
718 
719           if (transactionsLogger.isLoggable(Level.FINEST)) {
720               transactionsLogger.log(Level.FINEST,
721             "recorded new TxnManagerTransaction", txntr);
722         }
723 
724 
725       } catch(Exception e) {
726         if (transactionsLogger.isLoggable(Level.FINEST)) {
727               transactionsLogger.log(Level.FINEST,
728         "Problem creating transaction", e);
729         }
730         RuntimeException wrap =
731             new RuntimeException("Unable to create transaction", e);
732         transactionsLogger.throwing(
733             GroupTxnManagerImpl.class.getName(), "create", wrap);
734         throw wrap;
735     }
736 
737       TransactionManager.Created tmp =  
738         new TransactionManager.Created(str.id, txnmgrlease);               
739       if (operationsLogger.isLoggable(Level.FINER)) {
740           operationsLogger.exiting(
741         GroupTxnManagerImpl.class.getName(), "create", tmp);
742     }
743 
744       return tmp;
745   }
746 
747   public void
748       join(long id, TransactionParticipant part, long crashCount)
749       throws UnknownTransactionException, CannotJoinException,
750            CrashCountException, RemoteException
751   {
752       if (operationsLogger.isLoggable(Level.FINER)) {
753           operationsLogger.entering(
754         GroupTxnManagerImpl.class.getName(), "join", 
755             new Object[] {new Long(id), part, new Long(crashCount) });
756     }
757       readyState.check();
758 
759                                 
760       TransactionParticipant preparedTarget = null;
761       preparedTarget =
762           (TransactionParticipant)
763               participantPreparer.prepareProxy(part);
764       
765     if (participantLogger.isLoggable(Level.FINEST)) {
766           participantLogger.log(Level.FINEST, 
767             "prepared participant: {0}", preparedTarget);
768     }
769 
770       GroupTxnManagerTransaction txntr =
771         (GroupTxnManagerTransaction) txns.get(new Long(id));
772 
773     if (txntr == null)
774         throw new UnknownTransactionException("unknown transaction");
775 
776     // txntr.join does expiration check
777     txntr.join(preparedTarget, crashCount);
778       if (operationsLogger.isLoggable(Level.FINER)) {
779           operationsLogger.exiting(
780         GroupTxnManagerImpl.class.getName(), "join");
781     }
782   }
783 
784 
785   public int getState(long id)
786       throws UnknownTransactionException
787   {
788       if (operationsLogger.isLoggable(Level.FINER)) {
789           operationsLogger.entering(
790         GroupTxnManagerImpl.class.getName(), "getState", 
791             new Object[] {new Long(id)});
792     }
793       readyState.check();
794 
795       GroupTxnManagerTransaction txntr =
796             (GroupTxnManagerTransaction) txns.get(new Long(id));
797 
798     if (txntr == null)
799         throw new UnknownTransactionException("unknown transaction");
800       /* Expiration checks are only meaningful for active transactions. */
801       /* NOTE: 
802      * 1) Cancellation sets expiration to 0 without changing state 
803      * from Active right away. Clients are supposed to treat 
804      * UnknownTransactionException just like Aborted, so it's OK to send
805      * in this case. 
806      * 2) Might be a small window where client is committing the transaction
807      * close to the expiration time. If the committed transition takes
808      * place between getState() and ensureCurrent then the client could get
809      * a false result.
810      */
811 //TODO - need better locking here. getState and expiration need to be checked atomically    
812       int state = txntr.getState();
813       if (state == ACTIVE && !ensureCurrent(txntr)) 
814         throw new UnknownTransactionException("unknown transaction");
815         
816     if (operationsLogger.isLoggable(Level.FINER)) {
817           operationsLogger.exiting(
818         GroupTxnManagerImpl.class.getName(), "getState", 
819             new Integer(state));
820     }
821     return state;
822   }
823 
824 
825   public void commit(long id)
826       throws UnknownTransactionException, CannotCommitException,
827     RemoteException
828   {
829       if (operationsLogger.isLoggable(Level.FINER)) {
830           operationsLogger.entering(
831         GroupTxnManagerImpl.class.getName(), "commit", 
832             new Long(id));
833     }
834       readyState.check();
835 
836       try {
837           commit(id, 0);
838       } catch(TimeoutExpiredException tee) {
839         //This exception is swallowed because the
840         //commit with no timeout only schedules a
841         //roll-forward to happen
842       }
843       if (operationsLogger.isLoggable(Level.FINER)) {
844           operationsLogger.exiting(
845         GroupTxnManagerImpl.class.getName(), "commit");
846     }
847   }
848 
849   public void commit(long id, long waitFor)
850       throws UnknownTransactionException, CannotCommitException,
851            TimeoutExpiredException, RemoteException
852   {
853       //!! No early return when not synchronous
854       if (operationsLogger.isLoggable(Level.FINER)) {
855           operationsLogger.entering(
856         GroupTxnManagerImpl.class.getName(), "commit", 
857             new Object[] {new Long(id), new Long(waitFor)});
858     }
859       readyState.check();
860 
861     GroupTxnManagerTransaction txntr =
862         (GroupTxnManagerTransaction) txns.get(new Long(id));
863 
864     if (transactionsLogger.isLoggable(Level.FINEST)) {
865           transactionsLogger.log(Level.FINEST,
866             "Retrieved TxnManagerTransaction: {0}", txntr);
867     }
868 
869     if (txntr == null)
870         throw new UnknownTransactionException("Unknown transaction");
871 
872     // txntr.commit does expiration check
873     txntr.commit(waitFor);
874       txns.remove(new Long(id));
875 
876     if (transactionsLogger.isLoggable(Level.FINEST)) {
877           transactionsLogger.log(Level.FINEST,
878               "Committed transaction id {0}", new Long(id));
879     }
880       if (operationsLogger.isLoggable(Level.FINER)) {
881           operationsLogger.exiting(
882         GroupTxnManagerImpl.class.getName(), "commit");
883     }
884   }
885 
886 
887   public void abort(long id)
888     throws UnknownTransactionException, CannotAbortException
889   {
890       if (operationsLogger.isLoggable(Level.FINER)) {
891           operationsLogger.entering(
892         GroupTxnManagerImpl.class.getName(), "abort", 
893             new Object[] {new Long(id)});
894     }
895       readyState.check();
896       try {
897           abort(id, 0);
898       } catch(TimeoutExpiredException tee) {
899         //Swallow this exception because we only want to
900         //schedule a settler task
901       }
902       if (operationsLogger.isLoggable(Level.FINER)) {
903           operationsLogger.exiting(
904         GroupTxnManagerImpl.class.getName(), "abort");
905     }
906   }
907 
908   public void abort(long id, long waitFor)
909       throws UnknownTransactionException, CannotAbortException,
910            TimeoutExpiredException
911   {
912       if (operationsLogger.isLoggable(Level.FINER)) {
913           operationsLogger.entering(
914         GroupTxnManagerImpl.class.getName(), "abort", 
915             new Object[] {new Long(id), new Long(waitFor)});
916     }
917       readyState.check();
918       
919       //!! Multi-participants not supported
920       //!! No early return when not synchronous
921 
922 
923       // At this point, ask the Participants associated
924     // with the Transaction to prepare
925 
926     GroupTxnManagerTransaction txntr = 
927             (GroupTxnManagerTransaction) txns.get(new Long(id));
928 
929     if (transactionsLogger.isLoggable(Level.FINEST)) {
930           transactionsLogger.log(Level.FINEST,
931           "Retrieved TxnManagerTransaction: {0}", txntr);
932     }
933     /*
934      * Since lease cancellation process sets expiration to 0 
935      * and then calls abort, can't reliably check expiration 
936      * at this point.
937      */
938 //TODO - Change internal, lease logic to call overload w/o expiration check
939 //TODO - Add expiration check to abort for external clients     
940     if (txntr == null)
941         throw new CannotAbortException();
942 
943     txntr.abort(waitFor);
944     txns.remove(new Long(id));
945 
946     if (transactionsLogger.isLoggable(Level.FINEST)) {
947           transactionsLogger.log(Level.FINEST,
948               "aborted transaction id {0}", new Long(id));
949     }
950       if (operationsLogger.isLoggable(Level.FINER)) {
951           operationsLogger.exiting(
952         GroupTxnManagerImpl.class.getName(), "abort");
953     }
954   }
955 
956   //Satisfies the LogRecovery interface so that the
957   //TransactionManager can recover it's non-transient
958   //state in the face of process failure.
959 
960   /**
961    *  This method recovers state changes resulting from
962    *  committing a transaction.  This re-creates the
963    *  internal representation of the transaction.
964    * 
965    * @param cookie the transaction's ID
966    *
967    * @param rec the <code>LogRecord</code>
968    */
969   public void recover(long cookie, LogRecord rec) throws LogException {
970       if (operationsLogger.isLoggable(Level.FINER)) {
971           operationsLogger.entering(
972         GroupTxnManagerImpl.class.getName(), "recover", 
973             new Object[] {new Long(cookie), rec});
974     }
975     GroupTxnManagerTransaction tmt = enterTMT(cookie);
976     TxnLogRecord trec = (TxnLogRecord) rec;
977 //    trec.recover(tmt);
978       if (operationsLogger.isLoggable(Level.FINER)) {
979           operationsLogger.exiting(
980         GroupTxnManagerImpl.class.getName(), "recover");
981     }
982   }
983 
984 
985   /**
986    * Informs the transaction manager to attempt to
987    * settle a given transaction.
988    *
989    * @param tid the transaction's ID
990    */
991   public synchronized void noteUnsettledTxn(long tid) {
992       if (operationsLogger.isLoggable(Level.FINER)) {
993           operationsLogger.entering(GroupTxnManagerImpl.class.getName(), 
994             "noteUnsettledTxn", new Object[] {new Long(tid)});
995     }
996     unsettledtxns.add(new Long(tid));
997 
998     notifyAll();
999 
1000       if (operationsLogger.isLoggable(Level.FINER)) {
1001           operationsLogger.exiting(GroupTxnManagerImpl.class.getName(), 
1002             "noteUnsettledTxn");
1003     }
1004   }
1005 
1006   private synchronized void settleTxns() throws InterruptedException {
1007     ClientLog log = null;
1008 
1009       if (operationsLogger.isLoggable(Level.FINER)) {
1010           operationsLogger.entering(GroupTxnManagerImpl.class.getName(), 
1011             "settleTxns");
1012     }
1013     if (transactionsLogger.isLoggable(Level.FINEST)) {
1014           transactionsLogger.log(Level.FINEST,
1015               "Settling {0} transactions.", 
1016         new Integer(unsettledtxns.size()));
1017     }
1018 
1019     int numtxns = 0;
1020     Long first = null;
1021     long tid = 0;
1022 
1023     while (true) {
1024         numtxns = unsettledtxns.size();
1025 
1026         if (numtxns == 0) {
1027             if (transactionsLogger.isLoggable(Level.FINEST)) {
1028                   transactionsLogger.log(Level.FINEST,
1029                       "Settler waiting");
1030             }
1031         wait();
1032 
1033             if (transactionsLogger.isLoggable(Level.FINEST)) {
1034                   transactionsLogger.log(Level.FINEST,
1035                       "Settler notified");
1036             }
1037         continue;
1038         }
1039 
1040         first = null;
1041 
1042         first = (Long) unsettledtxns.firstElement();
1043         tid = first.longValue();
1044 
1045         SettlerTask task = 
1046             new SettlerTask(
1047             settlerpool, settlerWakeupMgr, this, tid);
1048         settlerpool.add(task);
1049         unsettledtxns.remove(first);
1050 
1051           if (settleThread.hasBeenInterrupted()) 
1052             throw new InterruptedException("settleTxns interrupted");
1053         
1054         if (transactionsLogger.isLoggable(Level.FINEST)) {
1055               transactionsLogger.log(Level.FINEST,
1056                   "Added SettlerTask for tid {0}", new Long(tid));
1057         }
1058     }
1059     // Not reachable
1060       /*
1061      * if (operationsLogger.isLoggable(Level.FINER)) {
1062           operationsLogger.exiting(GroupTxnManagerImpl.class.getName(), 
1063      *   "settleTxns");
1064      */
1065   }
1066 
1067 
1068   //TransactionParticipant interface go here
1069   //when I implement nested transactions
1070 
1071 
1072 
1073   /**
1074    *  Method from <code>TxnManager</code> which produces
1075    *  a <code>Transaction</code> from its ID.
1076    *
1077    * @param id the ID
1078    *
1079    * @see net.jini.core.transaction.Transaction
1080    * @see com.sun.jini.mahalo.TxnManager
1081    */
1082   public Transaction getTransaction(long id)
1083       throws UnknownTransactionException {
1084           
1085       readyState.check();
1086 
1087 
1088       if (id == ((long)-1))
1089           return null;
1090 
1091       // First consult the hashtable for the Object
1092       // containing all actions performed under a
1093       // particular transaction
1094 
1095       GroupTxnManagerTransaction txntr =
1096             (GroupTxnManagerTransaction) txns.get(new Long(id));
1097 
1098     if (txntr == null)
1099         throw new UnknownTransactionException("unknown transaction");
1100 
1101       Transaction tn = (Transaction) txntr.getTransaction();
1102       ServerTransaction tr = serverTransaction(tn);
1103 
1104       if (tr == null)
1105          throw new UnknownTransactionException(
1106                          "GroupTxnManagerImpl: getTransaction: "
1107                                            + "unable to find transaction(" +
1108                          id + ")");
1109 //TODO - use IDs vs equals                       
1110       if (!tr.mgr.equals(this))
1111           throw new UnknownTransactionException("wrong manager (" + tr.mgr +
1112                           " instead of " + this + ")");
1113 
1114       return tr;
1115   }
1116 
1117 
1118   /**
1119    * Requests the renewal of  a lease on a <code>Transaction</code>.
1120    *
1121    * @param cookie identifies the leased resource
1122    *
1123    * @param extension requested lease extension
1124    *
1125    * @see net.jini.core.lease.Lease
1126    * @see com.sun.jini.landlord.LeasedResource
1127    * @see com.sun.jini.mahalo.LeaseManager
1128    */
1129   public long renew(Uuid uuid, long extension)
1130             throws UnknownLeaseException, LeaseDeniedException
1131   {
1132 
1133       if (operationsLogger.isLoggable(Level.FINER)) {
1134           operationsLogger.entering(GroupTxnManagerImpl.class.getName(), "renew", 
1135             new Object[] {uuid, new Long(extension)});
1136     }
1137       readyState.check();
1138 
1139       verifyLeaseUuid(uuid);
1140     Long tid = getLeaseTid(uuid);
1141     GroupTxnManagerTransaction txntr =
1142             (GroupTxnManagerTransaction)txns.get(tid);
1143 
1144     if (txntr == null)
1145         throw new UnknownLeaseException();
1146 
1147     // synchronize on the resource so there is not a race condition
1148     // between renew and expiration
1149     Result r;
1150     synchronized (txntr) {
1151 //TODO - check for ACTIVE too?
1152 //TODO - if post-ACTIVE, do anything?   
1153         if (!ensureCurrent(txntr))
1154         throw new UnknownLeaseException("Lease already expired");
1155         long oldExpiration = txntr.getExpiration();
1156           r = txnLeasePeriodPolicy.renew(txntr, extension);
1157         txntr.setExpiration(r.expiration);
1158         expMgr.renewed(txntr);
1159           if (operationsLogger.isLoggable(Level.FINER)) {
1160               operationsLogger.exiting(
1161             GroupTxnManagerImpl.class.getName(), "renew", 
1162                 new Object[] {new Long(r.duration)});
1163         }
1164         return r.duration;
1165     }
1166   }
1167 
1168   /**
1169    * Cancels the lease on a <code>Transaction</code>.
1170    *
1171    * @param cookie identifies the leased resource
1172    *
1173    * @see net.jini.core.lease.Lease
1174    * @see com.sun.jini.landlord.LeasedResource
1175    * @see com.sun.jini.mahalo.LeaseManager
1176    */
1177   public void cancel(Uuid uuid) throws UnknownLeaseException {
1178 
1179       if (operationsLogger.isLoggable(Level.FINER)) {
1180           operationsLogger.entering(
1181         GroupTxnManagerImpl.class.getName(), "cancel", 
1182             new Object[] {uuid});
1183     }
1184       readyState.check();
1185     
1186       verifyLeaseUuid(uuid);
1187     Long tid = getLeaseTid(uuid);
1188     GroupTxnManagerTransaction txntr =
1189             (GroupTxnManagerTransaction) txns.get(tid);
1190 
1191     if (txntr == null)
1192         throw new UnknownLeaseException();
1193 
1194     int state = txntr.getState();
1195 
1196 /**
1197 * Add this back in once LeaseExpirationManager uses an overloaded version of cancel
1198 * that doesn't perform an expiration check.  LeaseExpirationManager calls cancel()
1199 * after the txn has expired, so can't reliably check expiration here.
1200 *
1201 //TODO - need better locking here. getState and expiration need to be checked atomically    
1202       if ( (state == ACTIVE && !ensureCurrent(txntr)) ||
1203          (state != ACTIVE))  
1204         throw new UnknownLeaseException("unknown transaction");
1205 **/     
1206 
1207     if (state == ACTIVE) {
1208 
1209         synchronized (txntr) {
1210             txntr.setExpiration(0); // Mark as done
1211         }
1212 
1213         try {
1214             abort(((Long)tid).longValue());
1215         } catch (TransactionException e) {
1216             throw new
1217             UnknownLeaseException("When canceling abort threw:" +
1218             e.getClass().getName() + ":" + e.getLocalizedMessage());
1219         }
1220     }
1221     
1222       if (operationsLogger.isLoggable(Level.FINER)) {
1223           operationsLogger.exiting(
1224         GroupTxnManagerImpl.class.getName(), "cancel");
1225     }
1226   }
1227 
1228   /**
1229    * Bulk renewal request of leases on <code>Transaction</code>s.
1230    *
1231    * @param cookies identifies the leased resources
1232    *
1233    * @param extensions requested lease extensions
1234    *
1235    * @see net.jini.core.lease.Lease
1236    * @see com.sun.jini.landlord.LeasedResource
1237    * @see com.sun.jini.mahalo.LeaseManager
1238    */
1239   public Landlord.RenewResults renewAll(Uuid[] cookies, long[] extensions) {
1240       if (operationsLogger.isLoggable(Level.FINER)) {
1241           operationsLogger.entering(
1242         GroupTxnManagerImpl.class.getName(), "renewAll");
1243     }
1244       readyState.check();
1245 
1246     Landlord.RenewResults results =
1247         LandlordUtil.renewAll(this, cookies, extensions);
1248       if (operationsLogger.isLoggable(Level.FINER)) {
1249           operationsLogger.exiting(
1250         GroupTxnManagerImpl.class.getName(), "renewAll");
1251     }
1252     return results;
1253   }
1254 
1255 
1256   /**
1257    * Bulk cancel of leases on <code>Transaction</code>s.
1258    *
1259    * @param cookies identifies the leased resources
1260    *
1261    * @see net.jini.core.lease.Lease
1262    * @see com.sun.jini.landlord.LeasedResource
1263    * @see com.sun.jini.mahalo.LeaseManager
1264    */
1265   public Map cancelAll(Uuid[] cookies) {
1266       if (operationsLogger.isLoggable(Level.FINER)) {
1267           operationsLogger.entering(
1268         GroupTxnManagerImpl.class.getName(), "cancelAll");
1269     }
1270       readyState.check();
1271 
1272     Map results = LandlordUtil.cancelAll(this, cookies);
1273       if (operationsLogger.isLoggable(Level.FINER)) {
1274           operationsLogger.exiting(
1275         GroupTxnManagerImpl.class.getName(), "cancelAll");
1276     }
1277     return results;
1278   }
1279 
1280   // local methods
1281 
1282   /**
1283    * gets the next available transaction ID.
1284    */
1285   static long nextID() {
1286       if (operationsLogger.isLoggable(Level.FINER)) {
1287           operationsLogger.entering(
1288         GroupTxnManagerImpl.class.getName(), "nextID");
1289     }
1290     long id;
1291     synchronized (idGen) {
1292         do {
1293         id = 0;
1294         idGen.nextBytes(idGenBuf);
1295         for (int i = 0; i < 8; i++)
1296             id = (id << 8) | (idGenBuf[i] & 0xFF);
1297         } while (id == 0);              // skip flag value
1298     }
1299       if (operationsLogger.isLoggable(Level.FINER)) {
1300           operationsLogger.exiting(GroupTxnManagerImpl.class.getName(), "nextID",
1301             new Long(id));
1302     }
1303     return id;
1304   }
1305 
1306 
1307 
1308   private ServerTransaction serverTransaction(Transaction baseTr)
1309       throws UnknownTransactionException
1310   {
1311       if (operationsLogger.isLoggable(Level.FINER)) {
1312           operationsLogger.entering(
1313         GroupTxnManagerImpl.class.getName(), 
1314             "serverTransaction", baseTr);
1315     }
1316       try {
1317           if (operationsLogger.isLoggable(Level.FINER)) {
1318               operationsLogger.exiting(GroupTxnManagerImpl.class.getName(), 
1319                 "serverTransaction", baseTr);
1320         }
1321           return (ServerTransaction) baseTr;
1322       } catch (ClassCastException e) {
1323           throw new UnknownTransactionException("unexpected transaction type");
1324       }
1325   }
1326 
1327 
1328   /**
1329    * Returns a reference to the <code>TransactionManager</code>
1330    * interface.
1331    *
1332    * @see net.jini.core.transaction.server.TransactionManager
1333    */
1334   public TransactionManager manager() {
1335       readyState.check();
1336 
1337       return txnMgrProxy;
1338   }
1339 
1340 
1341   private GroupTxnManagerTransaction enterTMT(long cookie) {
1342     Long key = new Long(cookie);
1343       if (operationsLogger.isLoggable(Level.FINER)) {
1344           operationsLogger.entering(GroupTxnManagerImpl.class.getName(), 
1345             "enterTMT", key);
1346     }
1347     GroupTxnManagerTransaction tmt =
1348             (GroupTxnManagerTransaction) txns.get(key);
1349 
1350     if (tmt == null) {
1351           Uuid uuid = createLeaseUuid(cookie);
1352         tmt = new GroupTxnManagerTransaction(
1353             txnMgrProxy, logmgr, cookie, taskpool, 
1354         taskWakeupMgr, this, uuid);
1355         noteUnsettledTxn(cookie);
1356         /* Since only aborted or committed txns are persisted,
1357          * their expirations are irrelevant. Therefore, any recovered
1358          * transactions are effectively lease.FOREVER. 
1359          */
1360     }
1361 
1362     txns.put(key, tmt);
1363 
1364       if (operationsLogger.isLoggable(Level.FINER)) {
1365           operationsLogger.exiting(GroupTxnManagerImpl.class.getName(), 
1366             "enterTMT", tmt);
1367     }
1368     return tmt;
1369   }
1370 
1371   //***********************************************************
1372   // Admin
1373 
1374   // Methods required by DestroyAdmin
1375 
1376   /**
1377    * Cleans up and exits the transaction manager.
1378    */
1379   public void destroy() {
1380       if (operationsLogger.isLoggable(Level.FINER)) {
1381           operationsLogger.entering(
1382         GroupTxnManagerImpl.class.getName(), "destroy");
1383     }
1384       readyState.check();
1385 
1386       (new DestroyThread()).start();
1387       if (operationsLogger.isLoggable(Level.FINER)) {
1388           operationsLogger.exiting(
1389         GroupTxnManagerImpl.class.getName(), "destroy");
1390     }
1391   }
1392 
1393   /** Maximum delay for unexport attempts */
1394   private final static long MAX_UNEXPORT_DELAY = 2 * MINUTES;
1395 
1396   /**
1397    * Termination thread code.  We do this in a separate thread to
1398    * avoid deadlock, because Activatable.inactive will block until
1399    * in-progress RMI calls are finished.
1400    */
1401   private class DestroyThread extends Thread {
1402 
1403       /** Create a non-daemon thread */
1404       public DestroyThread() {
1405           super("DestroyThread");
1406           /* override inheritance from RMI daemon thread */
1407           setDaemon(false);
1408       }
1409 
1410       public void run() {
1411           if (operationsLogger.isLoggable(Level.FINER)) {
1412              operationsLogger.entering(
1413            DestroyThread.class.getName(), "run");
1414         }
1415 
1416           Exception failed = null;
1417 
1418 /**TODO 
1419 * - move this block into the destroy() method and let the 
1420 *   remote ex pass through
1421 */
1422           if (activationPrepared) {     
1423             try {
1424                   if(destroyLogger.isLoggable(Level.FINEST)) {
1425                     destroyLogger.log(Level.FINEST,
1426                 "Unregistering object.");
1427                   }
1428             if (activationID != null)
1429                       activationSystem.unregisterObject(activationID);
1430         } catch (RemoteException e) {
1431             /* give up until we can at least unregister */
1432                   if(destroyLogger.isLoggable(Level.WARNING)) {
1433                     destroyLogger.log(Level.WARNING,
1434                 "Trouble unregistering object -- aborting.", e);
1435                   }
1436             return;
1437         } catch (ActivationException e) {
1438                   /*
1439                    * Activation system is shutting down or this
1440                    * object has already been unregistered --
1441                    * ignore in either case.
1442                    */
1443                   if(destroyLogger.isLoggable(Levels.HANDLED)) {
1444                     destroyLogger.log(Levels.HANDLED,
1445                 "Trouble unregistering object -- ignoring.", e);
1446                   }
1447         }
1448         }
1449 
1450           // Attempt to unexport this object -- nicely first
1451           if(destroyLogger.isLoggable(Level.FINEST)) {
1452             destroyLogger.log(Level.FINEST,
1453             "Attempting unforced unexport.");
1454           }
1455           long endTime =
1456               System.currentTimeMillis() + MAX_UNEXPORT_DELAY;
1457           if (endTime < 0) { // Check for overflow
1458               endTime = Long.MAX_VALUE;
1459           }
1460           boolean unexported = false;
1461 /**TODO 
1462 * - trap IllegalStateException from unexport
1463 */
1464           while ((!unexported) &&
1465                  (System.currentTimeMillis() < endTime)) {
1466               /* wait for any pending operations to complete */
1467               unexported = exporter.unexport(false);
1468               if (!unexported) {
1469                   if (destroyLogger.isLoggable(Level.FINEST)) {
1470                       destroyLogger.log(Level.FINEST, 
1471                           "Waiting for in-progress calls to complete");
1472                   }
1473                   try {
1474                       sleep(1000);
1475                   } catch (InterruptedException ie) {
1476                       if (destroyLogger.isLoggable(Levels.HANDLED)) {
1477                           destroyLogger.log(Levels.HANDLED, 
1478                               "problem unexporting nicely", ie);
1479                       }
1480                       break; //fall through to forced unexport
1481                  }
1482               } else {
1483                   if (destroyLogger.isLoggable(Level.FINEST)) {
1484                       destroyLogger.log(Level.FINEST, 
1485                           "Unexport completed");
1486                   }
1487               }      
1488           }
1489 
1490           // Attempt to forcefully unexport this object, if not already done
1491           if (!unexported) {
1492               if(destroyLogger.isLoggable(Level.FINEST)) {
1493                 destroyLogger.log(Level.FINEST,
1494                 "Attempting forced unexport.");
1495               }
1496         /* Attempt to forcefully export the service */
1497               unexported = exporter.unexport(true);
1498           }
1499 
1500           if(destroyLogger.isLoggable(Level.FINEST)) {
1501             destroyLogger.log(Level.FINEST,"Destroying JoinStateManager.");
1502         }
1503         try {
1504             joinStateManager.destroy();
1505           } catch (Exception t) {
1506               if(destroyLogger.isLoggable(Levels.HANDLED)) {
1507                 destroyLogger.log(Levels.HANDLED, 
1508                 "Problem destroying JoinStateManager", t);
1509         }
1510           }
1511 
1512         //
1513           // Attempt to stop all running threads
1514           //
1515           if(destroyLogger.isLoggable(Level.FINEST)) {
1516             destroyLogger.log(Level.FINEST,"Terminating lease expiration manager.");
1517         }
1518         expMgr.terminate();
1519 
1520           if(destroyLogger.isLoggable(Level.FINEST)) {
1521             destroyLogger.log(Level.FINEST,"Interrupting settleThread.");
1522           }
1523         settleThread.interrupt();
1524           try {
1525               settleThread.join();
1526           } catch (InterruptedException ie) {
1527               if(destroyLogger.isLoggable(Levels.HANDLED)) {
1528                 destroyLogger.log(Levels.HANDLED, 
1529                 "Problem stopping settleThread", ie);
1530         }
1531           }
1532 
1533           if(destroyLogger.isLoggable(Level.FINEST)) {
1534             destroyLogger.log(Level.FINEST,"Terminating settlerpool.");
1535           }
1536         settlerpool.terminate();
1537         settlerWakeupMgr.stop();
1538         settlerWakeupMgr.cancelAll();
1539 
1540           if(destroyLogger.isLoggable(Level.FINEST)) {
1541             destroyLogger.log(Level.FINEST,"Terminating taskpool.");
1542           }
1543         taskpool.terminate();
1544         taskWakeupMgr.stop();
1545           taskWakeupMgr.cancelAll();
1546 
1547 
1548         // Remove persistent store- ask LogManager to clean
1549         // itself up, then clean up the persistence path.
1550           if(destroyLogger.isLoggable(Level.FINEST)) {
1551             destroyLogger.log(Level.FINEST,"Destroying transaction logs.");
1552         }
1553         MultiLogManagerAdmin logadmin =
1554             (MultiLogManagerAdmin) logmgr.getAdmin();
1555     
1556         logadmin.destroy();
1557 
1558         if (persistent) {       
1559               if(destroyLogger.isLoggable(Level.FINEST)) {
1560                 destroyLogger.log(Level.FINEST,"Destroying persistence directory.");
1561             }
1562             try {
1563                 com.sun.jini.system.FileSystem.destroy(
1564                 new File(persistenceDirectory), true);
1565             } catch (IOException e) {
1566                   if(destroyLogger.isLoggable(Levels.HANDLED)) {
1567                     destroyLogger.log(Levels.HANDLED, 
1568                     "Problem destroying persistence directory", e);
1569             }
1570             }
1571         }
1572         
1573           if(activationID != null) {
1574             if(destroyLogger.isLoggable(Level.FINEST)) {
1575                 destroyLogger.log(Level.FINEST,"Calling Activatable.inactive.");
1576               }
1577             try {
1578                   Activatable.inactive(activationID);
1579               } catch (RemoteException e) { // ignore
1580                   if(destroyLogger.isLoggable(Levels.HANDLED)) {
1581                     destroyLogger.log(Levels.HANDLED, 
1582                     "Problem inactivating service", e);
1583             }
1584               } catch (ActivationException e) { // ignore
1585                   if(destroyLogger.isLoggable(Levels.HANDLED)) {
1586                     destroyLogger.log(Levels.HANDLED, 
1587                     "Problem inactivating service", e);
1588             }
1589               }
1590           }
1591 
1592         if (lifeCycle != null) {
1593             if(destroyLogger.isLoggable(Level.FINEST)) {
1594                 destroyLogger.log(Level.FINEST,
1595                 "Unregistering with LifeCycle.");
1596               }
1597         lifeCycle.unregister(GroupTxnManagerImpl.this);
1598         }
1599         
1600         if (loginContext != null) {
1601             try {
1602             if (destroyLogger.isLoggable(Level.FINEST)) {
1603                 destroyLogger.log(Level.FINEST,
1604                 "Logging out");
1605             }
1606             loginContext.logout();
1607             } catch (Exception e) {
1608             if (destroyLogger.isLoggable(Levels.HANDLED)) {
1609                 destroyLogger.log(Levels.HANDLED,
1610                 "Exception while logging out", 
1611                 e);
1612             }
1613             }
1614         }
1615           readyState.shutdown();
1616             
1617           if (operationsLogger.isLoggable(Level.FINER)) {
1618               operationsLogger.exiting(
1619             DestroyThread.class.getName(), "run");
1620         }
1621     }
1622   }
1623 
1624   /**
1625    * Returns the administration object for the
1626    * transaction manager.
1627    */
1628   public Object getAdmin() {
1629       if (operationsLogger.isLoggable(Level.FINER)) {
1630           operationsLogger.entering(
1631             GroupTxnManagerImpl.class.getName(), "getAdmin");
1632     }
1633       readyState.check();
1634 
1635       if (operationsLogger.isLoggable(Level.FINER)) {
1636           operationsLogger.exiting(
1637             GroupTxnManagerImpl.class.getName(), "getAdmin", txnMgrAdminProxy);
1638     }
1639     return txnMgrAdminProxy;
1640   }
1641 
1642   // Methods required by JoinAdmin
1643   // Inherit java doc from super type
1644   public Entry[] getLookupAttributes() {
1645       readyState.check();
1646 
1647     return joinStateManager.getLookupAttributes();
1648   }
1649 
1650   // Inherit java doc from super type
1651   public void addLookupAttributes(Entry[] attrSets) {
1652       readyState.check();
1653       
1654     joinStateManager.addLookupAttributes(attrSets);
1655   }
1656 
1657   // Inherit java doc from super type
1658   public void modifyLookupAttributes(Entry[] attrSetTemplates, 
1659                        Entry[] attrSets) 
1660   {
1661       readyState.check();
1662 
1663     joinStateManager.modifyLookupAttributes(attrSetTemplates, attrSets);
1664   }
1665 
1666   // Inherit java doc from super type
1667   public String[] getLookupGroups() {
1668       readyState.check();
1669 
1670     return joinStateManager.getLookupGroups();
1671   }
1672 
1673   // Inherit java doc from super type
1674   public void addLookupGroups(String[] groups) {
1675       readyState.check();
1676 
1677     joinStateManager.addLookupGroups(groups);
1678   }
1679 
1680   // Inherit java doc from super type
1681   public void removeLookupGroups(String[] groups) {
1682       readyState.check();
1683 
1684     joinStateManager.removeLookupGroups(groups);
1685   }
1686 
1687   // Inherit java doc from super type
1688   public void setLookupGroups(String[] groups) {
1689       readyState.check();
1690 
1691     joinStateManager.setLookupGroups(groups);
1692   }
1693 
1694   // Inherit java doc from super type
1695   public LookupLocator[] getLookupLocators() {
1696       readyState.check();
1697 
1698     return joinStateManager.getLookupLocators();
1699   }
1700 
1701   // Inherit java doc from super type
1702   public void addLookupLocators(LookupLocator[] locators) 
1703       throws RemoteException
1704   {
1705       readyState.check();
1706 
1707     joinStateManager.addLookupLocators(locators);
1708   }
1709 
1710   // Inherit java doc from super type
1711   public void removeLookupLocators(LookupLocator[] locators)  
1712       throws RemoteException
1713   {
1714       readyState.check();
1715 
1716       joinStateManager.removeLookupLocators(locators);
1717   }
1718 
1719   // Inherit java doc from super type
1720   public void setLookupLocators(LookupLocator[] locators)  
1721       throws RemoteException
1722   {
1723       readyState.check();
1724 
1725     joinStateManager.setLookupLocators(locators);
1726   }
1727 
1728 
1729   //***********************************************************
1730   // Startup
1731 
1732   /**
1733    * Create the service owned attributes for an Mahalo server
1734    */
1735   private static Entry[] attributesFor() {
1736     final Entry info = new ServiceInfo("Transaction Manager", 
1737         "Sun Microsystems, Inc.",  "Sun Microsystems, Inc.", 
1738         "21",
1739         "", "");
1740     
1741     final Entry type = 
1742         new com.sun.jini.lookup.entry.BasicServiceType("Transaction Manager");
1743 
1744     return new Entry[]{info, type};
1745   }
1746 
1747   public Object getProxy() {
1748       if (operationsLogger.isLoggable(Level.FINER)) {
1749           operationsLogger.entering(
1750             GroupTxnManagerImpl.class.getName(), "getProxy");
1751     }
1752       if (operationsLogger.isLoggable(Level.FINER)) {
1753           operationsLogger.exiting(
1754             GroupTxnManagerImpl.class.getName(), "getProxy", serverStub);
1755     }
1756     return serverStub;
1757   }
1758   
1759   /* inherit javadoc */
1760   public Object getServiceProxy() {
1761       if (operationsLogger.isLoggable(Level.FINER)) {
1762           operationsLogger.entering(
1763             GroupTxnManagerImpl.class.getName(), "getServiceProxy");
1764     }
1765       readyState.check();
1766 
1767       if (operationsLogger.isLoggable(Level.FINER)) {
1768           operationsLogger.exiting(
1769             GroupTxnManagerImpl.class.getName(), "getServiceProxy", 
1770             txnMgrProxy);
1771     }
1772       return txnMgrProxy;
1773   }
1774 
1775   /**
1776    * Log information about failing to initialize the service and rethrow the
1777    * appropriate exception.
1778    *
1779    * @param e the exception produced by the failure
1780    */
1781   protected void initFailed(Throwable e) throws Exception {
1782       if (operationsLogger.isLoggable(Level.FINER)) {
1783           operationsLogger.entering(
1784             GroupTxnManagerImpl.class.getName(), "initFailed");
1785     }
1786       if(initLogger.isLoggable(Level.SEVERE)) {
1787           initLogger.log(Level.SEVERE, "Mahalo failed to initialize", e);
1788       }
1789       if (operationsLogger.isLoggable(Level.FINER)) {
1790           operationsLogger.exiting(
1791             GroupTxnManagerImpl.class.getName(), "initFailed");
1792     }
1793     if (e instanceof Exception) {
1794           throw (Exception) e;
1795       } else if (e instanceof Error) {
1796           throw (Error) e;
1797       } else {
1798           IllegalStateException ise =
1799               new IllegalStateException(e.getMessage());
1800           ise.initCause(e);
1801           throw ise;
1802       }
1803   }
1804   
1805   /*
1806    *
1807    */
1808   private void cleanup() {
1809       if (operationsLogger.isLoggable(Level.FINER)) {
1810           operationsLogger.entering(
1811         GroupTxnManagerImpl.class.getName(), "cleanup");
1812     }
1813 //TODO - add custom logic   
1814       if (serverStub != null) { // implies that exporter != null
1815         try {
1816               if(initLogger.isLoggable(Level.FINEST)) {
1817                 initLogger.log(Level.FINEST, "Unexporting service");            
1818             }
1819         exporter.unexport(true);
1820         } catch (Throwable t) {
1821               if(initLogger.isLoggable(Levels.HANDLED)) {
1822                 initLogger.log(Levels.HANDLED, "Trouble unexporting service", t);
1823         }           
1824         }
1825     }
1826     
1827     if (settlerpool != null)  {
1828            if(initLogger.isLoggable(Level.FINEST)) {
1829              initLogger.log(Level.FINEST, "Terminating settlerpool.");
1830            }
1831          try {
1832               settlerpool.terminate();
1833             if (settlerWakeupMgr != null)  {
1834                   if(initLogger.isLoggable(Level.FINEST)) {
1835                      initLogger.log(Level.FINEST, 
1836                  "Terminating settlerWakeupMgr.");
1837                 }
1838             settlerWakeupMgr.stop();
1839                 settlerWakeupMgr.cancelAll();
1840         }
1841           } catch (Throwable t) {
1842               if(initLogger.isLoggable(Levels.HANDLED)) {
1843                 initLogger.log(Levels.HANDLED, 
1844                 "Trouble terminating settlerpool", t);
1845         }           
1846           }
1847       }
1848     
1849     if (taskpool != null)  {
1850            if(initLogger.isLoggable(Level.FINEST)) {
1851             initLogger.log(Level.FINEST,"Terminating taskpool.");
1852          }
1853          try {
1854               taskpool.terminate();
1855             if (taskWakeupMgr != null)  {
1856                   if(initLogger.isLoggable(Level.FINEST)) {
1857                      initLogger.log(Level.FINEST,
1858                  "Terminating taskWakeupMgr.");
1859             }
1860             taskWakeupMgr.stop();
1861             taskWakeupMgr.cancelAll();
1862         }
1863           } catch (Throwable t) {
1864               if(initLogger.isLoggable(Levels.HANDLED)) {
1865                 initLogger.log(Levels.HANDLED, 
1866                 "Trouble terminating taskpool", t);
1867         }           
1868           }
1869       }
1870     
1871     if (settleThread != null) {
1872           if(initLogger.isLoggable(Level.FINEST)) {
1873             initLogger.log(Level.FINEST, "Interrupting settleThread.");
1874           }
1875         try {
1876               settleThread.interrupt();
1877           } catch (Throwable t) {
1878               if(initLogger.isLoggable(Levels.HANDLED)) {
1879                 initLogger.log(Levels.HANDLED, 
1880                 "Trouble terminating settleThread", t); 
1881         }       
1882           }
1883     }
1884     
1885       if (expMgr != null)  {
1886           if(initLogger.isLoggable(Level.FINEST)) {
1887             initLogger.log(Level.FINEST, 
1888             "Terminating lease expiration manager.");
1889           }
1890         expMgr.terminate();
1891     }
1892 
1893       if(initLogger.isLoggable(Level.FINEST)) {
1894         initLogger.log(Level.FINEST,"Destroying JoinStateManager.");
1895       }
1896     try {
1897         if (joinStateManager != null) {
1898         joinStateManager.stop();
1899         }
1900       } catch (Exception t) {
1901           if(initLogger.isLoggable(Levels.HANDLED)) {
1902             initLogger.log(Levels.HANDLED, 
1903                 "Problem destroying JoinStateManager", t);
1904           }
1905     }
1906     
1907       if (operationsLogger.isLoggable(Level.FINER)) {
1908           operationsLogger.exiting(
1909         GroupTxnManagerImpl.class.getName(), "cleanup");
1910     }
1911   }
1912   
1913   //////////////////////////////////////////
1914   // ProxyTrust Method
1915   //////////////////////////////////////////
1916   public TrustVerifier getProxyVerifier( ) {
1917       if (operationsLogger.isLoggable(Level.FINER)) {
1918           operationsLogger.exiting(GroupTxnManagerImpl.class.getName(), 
1919             "getProxyVerifier");
1920     }
1921       readyState.check();
1922 
1923       /* No verifier if the server isn't secure */
1924       if (!(txnMgrProxy instanceof RemoteMethodControl)) {
1925           throw new UnsupportedOperationException();
1926       } else {
1927           if (operationsLogger.isLoggable(Level.FINER)) {
1928               operationsLogger.exiting(GroupTxnManagerImpl.class.getName(), 
1929                 "getProxyVerifier");
1930         }
1931           return new ProxyVerifier(serverStub, topUuid);
1932       }
1933   }
1934 
1935 
1936   /**
1937    * Utility method that check for valid resource
1938    */
1939   private static boolean ensureCurrent(LeasedResource resource) {
1940       return resource.getExpiration() > System.currentTimeMillis();
1941   }
1942 
1943   /*
1944    * Attempt to build "real" Uuid from 
1945    * topUuid.getLeastSignificantBits(), which contains
1946    * the variant field, and the transaction id, which
1947    * should be unique for this service. Between the two 
1948    * of these, the Uuid should be unique.
1949    */
1950   private Uuid createLeaseUuid(long txnId) {
1951       return UuidFactory.create(
1952         topUuid.getLeastSignificantBits(),
1953         txnId);
1954   }
1955 
1956   private void verifyLeaseUuid(Uuid uuid) throws UnknownLeaseException {
1957     /*
1958      * Note: Lease Uuid contains
1959      * - Most Sig => the least sig bits of topUuid
1960      * - Least Sig => the txn id
1961      */
1962     // Check to if this server granted the resource
1963     if (uuid.getMostSignificantBits() != 
1964         topUuid.getLeastSignificantBits()) 
1965     {
1966         throw new UnknownLeaseException();
1967     }
1968 
1969   }
1970 
1971   private Long getLeaseTid(Uuid uuid) {
1972     // Extract the txn id from the lower bits of the uuid
1973       return new Long(uuid.getLeastSignificantBits());
1974   }
1975   
1976   
1977   /**
1978    * Getter method for LogManager. 
1979    */
1980   protected LogManager getLogManager() {
1981     return this.logmgr;
1982   }
1983 
1984   /**
1985    * Getter method for TaskManager.
1986    */
1987   protected TaskManager getTaskManager() {
1988     return this.taskpool;
1989   }
1990   
1991   /**
1992    * Getter method for WakeupManager.
1993    */
1994   protected WakeupManager getWakeupManager() {
1995     return this.taskWakeupMgr;
1996   }
1997   
1998 } // END GroupTxnManagerImpl