001//
002// Name
003//  $RCSfile: PartitionManager.java,v $
004// 
005// Copyright
006//      Copyright 2010-2012 Cloud Software Group, Inc. ALL RIGHTS RESERVED. 
007//      Cloud Software Group, Inc. Confidential Information
008//
009// History
010//  $Revision: 1.1.2.32 $ $Date: 2012/04/25 21:02:03 $
011//
012package com.kabira.platform.highavailability;
013
014import com.kabira.platform.ManagedObject;
015import com.kabira.platform.ManagedClassError;
016import com.kabira.platform.ResourceUnavailableException;
017import com.kabira.platform.Transaction;
018import com.kabira.platform.Transaction.Rollback;
019import com.kabira.platform.disteng.DEPartitionManager;
020import com.kabira.platform.disteng.DEPartitionNotifier;
021import com.kabira.platform.disteng.DEPartition;
022import com.kabira.platform.disteng.DEProperties;
023import com.kabira.platform.disteng.DEPartitionNotFound;
024import com.kabira.platform.disteng.DENodeMismatch;
025import com.kabira.platform.disteng.DENotActiveNode;
026import com.kabira.platform.disteng.DEEnableAction;
027import com.kabira.platform.disteng.DEDisableAction;
028import com.kabira.platform.disteng.DEMapperAudit;
029import com.kabira.platform.disteng.PartitionState;
030import com.kabira.platform.disteng.DEReplicationType;
031
032import java.util.HashSet;
033import java.util.logging.LogManager;
034import java.util.logging.Logger;
035import java.util.logging.Level;
036
037/**
038 * The PartitionManager class. This is the main interface for managing
039 * partitions. 
040 */
041public final class PartitionManager 
042{
043    /**
044     * An enumeration of the possible actions that can be taken when
045     * enabling high availability for a node.
046     */
047    public enum EnableAction
048    {
049        /**
050         * Perform any object migrations needed to activate the
051         * partitions defined for this node. This option should be used
052         * for newly added nodes.
053         */
054        JOIN_CLUSTER,
055        /**
056         * Before migrating the partition data, remove any instances that
057         * exist on the local node. This option should be used for nodes
058         * where the local data doesn't need to be preserved.
059         */
060        JOIN_CLUSTER_PURGE,
061        /**
062         * For all partitions where the node joining the cluster is the
063         * current active node, access the remote node where the
064         * partition should be restored from, and compare the objects
065         * between the local node and remote node. If an object on the
066         * local node has been updated, or a new object created, push the
067         * update to the remote node. If the remote node has been
068         * updated, replicate the change to the local node. If a state
069         * conflict is detected, or a duplicate key is found, remove the
070         * local object and copy the remote node object to the local
071         * node.
072         * <p>
073         * For all partitions where the node joining the cluster is
074         * acting as a replica, the replica objects are deleted and
075         * instances migrated from the remote node.
076         * <p>
077         * Warning: This is performance hostile, and should only be done
078         * when resolving a split-brain scenario in a cluster.
079         * 
080         * @see Partition.Properties#restoreFromNode(String)
081         */
082        JOIN_CLUSTER_RESTORE
083    }
084
085    /**
086     * An enumeration of the possible actions that can be taken when
087     * disabling high availability for a node.
088     */
089    public enum DisableAction
090    {
091        /**
092         * Perform any object migrations needed to migrate
093         * partitions owned by this node to the first order
094         * replica node, and remove this node from all replica
095         * lists. Before the disable is executed, an audit is
096         * done to insure that no partitions will be moved to the
097         * UNAVAILABLE state.
098         */
099        LEAVE_CLUSTER,
100        /**
101         * Same as LEAVE_CLUSTER, but with audits disabled.
102         */
103        LEAVE_CLUSTER_FORCE,
104    }
105
106    /**
107     * Find all partitions on the local node.
108     * 
109     * @return Array of Partition instances.
110     */
111    public static final Partition [] getPartitions()
112    {
113        DEPartitionManager pm = new DEPartitionManager();
114        DEPartition [] dp = pm.getPartitions();
115
116        Partition [] pa = new Partition[dp.length];
117
118        for (int i = 0; i < dp.length; i++)
119        {
120            pa[i] = new Partition(dp[i].name, dp[i].nodes,
121                dp[i].replicationTypeList,
122                mapState(dp[i].state), dp[i].lastUpdated,
123                dp[i].forceReplication, dp[i].objectChunks);
124        }
125        return pa;
126    }
127
128    /**
129     * Find a partition by name.
130     * 
131     * @param partitionName Name of partition to find.
132     *
133     * @return Partition instance.
134     *
135     * @exception PartitionNotFound
136     *  The given partition doesn't exist.
137     */
138    public static final Partition getPartition(
139        final String partitionName) throws PartitionNotFound
140    {
141        DEPartitionManager pm = new DEPartitionManager();
142
143        try
144        {
145            DEPartition p = pm.getPartition(partitionName);
146            return new Partition(p.name, p.nodes, p.replicationTypeList,
147                mapState(p.state), p.lastUpdated,
148                p.forceReplication, p.objectChunks);
149        }
150        catch (DEPartitionNotFound ex)
151        {
152            throw new PartitionNotFound(ex.errMsg);
153        }
154    }
155
156    /**
157     * Get the partition an object is in.
158     * 
159     * @param obj Object to examine.
160     *
161     * @return Partition instance.
162     *
163     * @exception PartitionNotFound
164     *  The given object isn't in a partition. This can happen if
165     *  object instances were created before a {@link PartitionMapper} was
166     *  installed for a managed class, or no mapper was ever defined.
167     */
168    public static final Partition getObjectPartition(
169        final Object obj) throws PartitionNotFound
170    {
171        DEPartitionManager pm = new DEPartitionManager();
172
173        String  partitionName = pm.getObjectPartition(obj);
174        if (partitionName.length() == 0)
175        {
176            throw new PartitionNotFound(
177                "No Partition exists for object '" + obj + "'");
178        }
179
180        return getPartition(partitionName);
181    }
182
183    /**
184     * Use {@link #definePartition(String, Partition.Properties, String, ReplicaNode [])}
185     */
186    @Deprecated
187    public static final void definePartition(
188        final String partitionName,
189        final String [] nodes) throws IllegalArgumentException
190    {
191        DEPartitionManager pm = new DEPartitionManager();
192
193        if (partitionName.isEmpty())
194        {
195            throw new IllegalArgumentException("partitionName cannot be empty");
196        }
197        validateNodeList(nodes);
198
199        DEReplicationType [] rtl = new DEReplicationType[nodes.length];
200        for (int i = 0; i < rtl.length; i++)
201        {
202            rtl[i] = DEReplicationType.DataSynchronous;
203        }
204
205        DEProperties props = new DEProperties();
206        props.forceReplication = false;
207        props.objectChunks = Partition.DefaultObjectsLockedPerTransaction;
208
209        pm.definePartition(partitionName, props, nodes, rtl);
210    }
211
212    /**
213     * Use {@link #definePartition(String, Partition.Properties, String, ReplicaNode [])}
214     * instead.
215     */
216    @Deprecated
217    public static final void definePartition(
218        final String partitionName,
219        final String restoreFromNode,
220        final String [] nodes) throws IllegalArgumentException, NotActiveNode
221    {
222        DEPartitionManager pm = new DEPartitionManager();
223
224        if (restoreFromNode.isEmpty())
225        {
226            throw new IllegalArgumentException(
227                "restoreFromNode cannot be empty");
228        }
229
230        try
231        {
232            pm.restorePartition(partitionName, restoreFromNode);
233        }
234        catch (DENotActiveNode ex)
235        {
236            throw new NotActiveNode(ex.errMsg);
237        }
238
239        definePartition(partitionName, nodes);
240    }
241
242    /**
243     * Define a partition.
244     * <p>
245     * This method defines a partition. This method can be called on any
246     * node in the cluster to define a new partition, or change the   
247     * definition of a pre-existing partition. Once all partitions have
248     * been defined for a node, the enablePartitions() method should be
249     * called to move the partitions to the active state. It is a runtime
250     * error to attempt to create or modify instances in a newly defined
251     * partition until the enablePartitions() method is called.
252     * <p>
253     * The activeNode is the node that will be the active node for the
254     * partition after it is enabled.
255     * <p>
256     * Optional partition properties can be used by passing in an
257     * instance of the {@link Partition.Properties} class. User defined
258     * properties such as forceReplication are saved and subsequently 
259     * used during failover processing. 
260     * <p>
261     * The replicas array contains an ordered list of replica nodes for
262     * the partition. A failure of the active node results in the
263     * partition automatically migrating to the the next entry in the
264     * replicas array. Objects are sent to each replica node based on
265     * the configuration in the ReplicaNode entry.
266     *
267     * @param partitionName Name of partition to create.
268     * @param partitionProperties Optional properties for the partition.
269     *  If null, the default property values are used.
270     * @param activeNode The active node for the partition
271     * @param replicas An ordered list of replica nodes for the partition.
272     *  Should be passed in as a null instance or a zero length array if
273     *  no replicas are desired.
274     *
275     * @exception IllegalArgumentException
276     *  The partitionName or activeNode was empty, or the replicas array
277     *  was invalid.
278     * @exception NotActiveNode
279     *  The current node is not the active node for the partition and 
280     *  restoreFromNode was defined in partitionProperties.
281     */
282    public static final void definePartition(
283        final String partitionName,
284        final Partition.Properties partitionProperties,
285        final String activeNode,
286        final ReplicaNode [] replicas)
287            throws IllegalArgumentException, NotActiveNode
288    {
289        DEPartitionManager pm = new DEPartitionManager();
290
291        if (partitionName.isEmpty())
292        {
293            throw new IllegalArgumentException("partitionName cannot be empty");
294        }
295
296        if (activeNode.isEmpty())
297        {
298            throw new IllegalArgumentException("activeNode cannot be empty");
299        }
300
301        validateReplicaList(replicas);
302
303        int nodeLength = (replicas == null) ? 1 : replicas.length + 1;
304
305        String [] nodes = new String[nodeLength];
306
307        nodes[0] = activeNode;
308        if (replicas != null)
309        {
310            for (int i = 0; i < replicas.length; i++)
311            {
312                assert( i + 1 < nodeLength );
313                nodes[i + 1] = replicas[i].nodeName;
314            }
315        }
316        validateNodeList(nodes);
317
318        DEReplicationType [] rtl = new DEReplicationType[nodes.length];
319        rtl[0] = DEReplicationType.DataSynchronous;
320        if (replicas != null)
321        {
322            for (int i = 0; i < replicas.length; i++)
323            {
324                rtl[i + 1] = mapReplicationType(replicas[i].replicationType);
325            }
326        }
327
328        if (partitionProperties != null &&
329            partitionProperties.m_restoreFromNode != null)
330        {
331            try
332            {
333                pm.restorePartition(partitionName,
334                    partitionProperties.m_restoreFromNode);
335            }
336            catch (DENotActiveNode ex)
337            {
338                throw new NotActiveNode(ex.errMsg);
339            }
340        }
341
342        DEProperties props = new DEProperties();
343        if (partitionProperties == null)
344        {
345            props.forceReplication = false;
346            props.objectChunks = Partition.DefaultObjectsLockedPerTransaction;
347        }
348        else
349        {
350            props.forceReplication = partitionProperties.m_forceReplication;
351            props.objectChunks =
352                partitionProperties.m_objectsLockedPerTransaction;
353        }
354
355        pm.definePartition(partitionName, props, nodes, rtl);
356    }
357
358    /**
359     * Associate a mapper to a given managed class.
360     *
361     * This method uses {@link PartitionMapper.Properties.Audit#IGNORE_PARTITIONING}
362     * for audit.
363     * 
364     * @param managedClass Name of a Managed object.
365     * @param partitionMapper User defined PartitionMapper instance.
366     *
367     * @exception ManagedClassError
368     *  The managedClass is not a Managed object, or has a non-abstract
369     *  parent class with a key defined but no mapper installed.
370     *
371     * @see PartitionManager#setMapper(Class, PartitionMapper, PartitionMapper.Properties)
372     */
373    public static final void setMapper(
374        final Class managedClass,
375        final PartitionMapper partitionMapper) throws ManagedClassError
376    {
377        setMapper(managedClass, partitionMapper, null);
378    }
379
380    /**
381     * Associate a mapper to a given managed class.
382     * <p>
383     * This method is used to associate a user defined PartitionMapper
384     * instance with a managed class. All managed types that are replicated 
385     * must have a PartitionMapper associated with the class. This method must
386     * be called on all nodes that has the partition definition.
387     * <p>
388     * The same PartitionMapper instance can be associated with multiple
389     * classes. If a PartitionMapper is already associated with a class,
390     * the new value overwrites the previous one.
391     * <p>
392     * Applications should only delete a PartitionMapper instance after a
393     * new instance is installed. If a PartitionMapper instance is
394     * deleted, any subsequent object creates for the class will throw a
395     * runtime exception.
396     * <p>
397     * Mapper instances are inherited by all classes that extend the
398     * managed class. If the managed class extends another class, that
399     * class must not have any keys defined.
400     * <p>
401     * If auditing is enabled in the mapperProperties instance, this routine
402     * will verify that no unpartitioned instances of the given class
403     * already exist in shared memory. This can happen if instances are
404     * created before a mapper is installed.
405     * 
406     * @param managedClass Name of a Managed object.
407     * @param partitionMapper User defined PartitionMapper instance.
408     * @param mapperProperties Optional mapper properties. If null,
409     * {@link PartitionMapper.Properties.Audit#IGNORE_PARTITIONING} is used.
410     *
411     * @exception ManagedClassError
412     *  The managedClass is not a Managed object, or has a non-abstract
413     *  parent class with a key defined but no mapper installed.
414     */
415    public static final void setMapper(
416        final Class managedClass,
417        final PartitionMapper partitionMapper,
418        final PartitionMapper.Properties mapperProperties)
419            throws ManagedClassError
420    {
421        DEPartitionManager pm = new DEPartitionManager();
422        try
423        {
424
425            DEMapperAudit audit = (mapperProperties == null)
426                ? DEMapperAudit.MapperIgnore
427                : mapAudit(mapperProperties.getAudit());
428            pm.setMapperAudit(managedClass.getName(), partitionMapper, audit);
429        }
430        catch (ResourceUnavailableException ex)
431        {
432            throw new ManagedClassError(ex.getMessage());
433        }
434    }
435
436    /**
437     * Determine if a class has a partition mapper installed.
438     * <p>
439     * This method determines if a user defined PartitionMapper
440     * instance is associated with a managed class.
441     * 
442     * @param managedClass Name of a Managed object.
443     *
444     * @return True if class has a mapper instance installed, otherwise false.
445     *
446     * @exception ManagedClassError
447     *  The managedClass is not a Managed object.
448     */
449    public static final boolean isPartitioned(final Class managedClass)
450    {
451        DEPartitionManager pm = new DEPartitionManager();
452        try
453        {
454            return pm.isPartitioned(managedClass.getName());
455        }
456        catch (ResourceUnavailableException ex)
457        {
458            throw new ManagedClassError(ex.getMessage());
459        }
460    }
461
462    /**
463     * Enable high availability for the all partitions defined on the node.
464     * <p>
465     * This method is used to enable all partitions that have been
466     * defined for this node. This method should be called each time new
467     * partitions are defined.
468     *
469     * @param enableAction The action to take when enabling partitions.
470     *
471     * @exception ResourceUnavailableException
472     *  The minimum number of nodes needed to establish a quorum has not been
473     *  seen.
474     */
475    public static final void enablePartitions(
476        final EnableAction enableAction) throws ResourceUnavailableException
477    {
478        DEPartitionManager pm = new DEPartitionManager();
479        pm.enablePartitions(mapEnable(enableAction));
480    }
481
482    /**
483     * Disable high availability for the all partitions defined on the node.
484     * <p>
485     * This method is used to remove the local node from the node list of
486     * all partitions that have been defined for this node. If called
487     * multiple times, the additional calls have no effect.
488     *
489     * @param disableAction  The action to take when disabling partitions.
490     *
491     * @exception ResourceUnavailableException
492     *  One or more partitions would be in the UNAVAILABLE state after the
493     *  disable executes, not thrown if LEAVE_CLUSTER_FORCE is used.
494     */
495    public static final void disablePartitions(
496        final DisableAction disableAction) throws ResourceUnavailableException
497    {
498        DEPartitionManager pm = new DEPartitionManager();
499        pm.disablePartitions(mapDisable(disableAction));
500    }
501
502    /**
503     * Wait for a remote node to become available.
504     *
505     * @param remoteNode name of remote node to wait for.
506     *
507     * @exception ResourceUnavailableException
508     *  The operation timed out waiting for the remote node to become
509     *  available.
510     */
511    public static final void waitForNode(
512        final String remoteNode) throws ResourceUnavailableException
513    {
514        DEPartitionManager pm = new DEPartitionManager();
515        pm.waitForNode(remoteNode);
516    }
517
518    //
519    // Package private methods. These don't bother with handling
520    // PartitionNotFound exceptions, since they can only be called after
521    // getPartition() was executed, and we never delete partitions.
522    //
523
524    static final void updatePartition(
525        final String existingPartition,
526        DEProperties props) throws NodeMismatch, NotActiveNode
527    {
528        DEPartitionManager pm = new DEPartitionManager();
529        try
530        {
531            pm.updatePartition(existingPartition, props);
532        }
533        catch (DENodeMismatch ex)
534        {
535            throw new NodeMismatch(ex.errMsg);
536        }
537        catch (DENotActiveNode ex)
538        {
539            throw new NotActiveNode(ex.errMsg);
540        }
541    }
542
543    static final void migratePartition(
544        final String partitionName,
545        DEProperties props,
546        final String [] nodes,
547        DEReplicationType [] rtl) throws NotActiveNode
548    {
549        DEPartitionManager pm = new DEPartitionManager();
550        try
551        {
552            pm.migratePartition(partitionName, props, nodes, rtl);
553        }
554        catch (DENotActiveNode ex)
555        {
556            throw new NotActiveNode(ex.errMsg);
557        }
558    }
559
560    static final void setNotifier(
561        final String partitionName,
562        final PartitionNotifier partitionNotifier) throws PartitionNotFound
563    {
564        Notifier notifier = new Notifier(partitionNotifier);
565        DEPartitionManager pm = new DEPartitionManager();
566        pm.setNotifier(partitionName, notifier);
567    }
568
569    //
570    // Validate the replica list. We just check for null replicas here, the
571    // rest of the checks are done in validateNodeList()
572    //
573    static void validateReplicaList(
574        final ReplicaNode [] replicas) throws IllegalArgumentException
575    {
576        if (replicas != null)
577        {
578            for (int i = 0; i < replicas.length; i++)
579            {
580                ReplicaNode r = replicas[i];
581                if (r == null)
582                {
583                    throw new IllegalArgumentException(
584                        "Replica entry at index " + i + " cannot be empty");
585                }
586            }
587        }
588    }
589
590    //
591    // Validate the node list
592    //
593    static void validateNodeList(
594        final String [] nodes) throws IllegalArgumentException
595    {
596        HashSet<String>     nodeSet = new HashSet<String>();
597        if (nodes.length == 0)
598        {
599            throw new IllegalArgumentException(
600                "The node array must have at least one entry");
601        }
602        for (int i = 0; i < nodes.length; i++)
603        {
604            String node = nodes[i];
605            if (node == null || node.isEmpty())
606            {
607                throw new IllegalArgumentException("Node entry at index " +
608                    i + " cannot be empty");
609            }
610            if (nodeSet.contains(node))
611            {
612                throw new IllegalArgumentException("Node entry " + node +
613                    " at index " + i + " is a duplicate. " +
614                    "All nodes must be unique");
615            }
616            nodeSet.add(node);
617        }
618    }
619
620    //
621    // Wrapper class to map notifiers.
622    //
623    private static class Notifier extends DEPartitionNotifier
624    {
625        public void stateChange(
626                final String partitionName,
627                final PartitionState oldState,
628                final PartitionState newState)
629        {
630            if (ManagedObject.isEmpty(m_partitionNotifier))
631            {
632                ManagedObject.delete(this);
633            }
634            else
635            {
636                m_partitionNotifier.stateChange(
637                    partitionName,
638                    mapState(oldState),
639                    mapState(newState));
640            }
641        }
642
643        Notifier(PartitionNotifier partitionNotifier)
644        {
645            m_partitionNotifier = partitionNotifier;
646        }
647
648        PartitionNotifier   m_partitionNotifier;
649    }
650
651    //
652    // Utility function to map PartitionState to Partition.State
653    //
654    private static Partition.State mapState(PartitionState state)
655    {
656        Partition.State s;
657
658        switch (state)
659        {
660        case PartitionInitial:
661            s = Partition.State.INITIAL;
662            break;
663        case PartitionActive:
664            s = Partition.State.ACTIVE;
665            break;
666        case PartitionUpdating:
667            s = Partition.State.UPDATING;
668            break;
669        case PartitionMigrating:
670            s = Partition.State.MIGRATING;
671            break;
672        case PartitionReplicating:
673            s = Partition.State.REPLICATING;
674            break;
675        default:
676            assert( state == PartitionState.PartitionUnavailable );
677            s = Partition.State.UNAVAILABLE;
678            break;
679        }
680        return s;
681    }
682
683    //
684    // Utility function to map PartitionManager.EnableAction to EnableAction
685    //
686    private static DEEnableAction mapEnable(
687        final PartitionManager.EnableAction enableAction)
688    {
689        DEEnableAction e;
690
691        switch (enableAction)
692        {
693        case JOIN_CLUSTER:
694            e = DEEnableAction.JoinCluster;
695            break;
696        case JOIN_CLUSTER_PURGE:
697            e = DEEnableAction.JoinClusterPurge;
698            break;
699        default:
700            assert( enableAction == EnableAction.JOIN_CLUSTER_RESTORE );
701            e = DEEnableAction.JoinClusterRestore;
702            break;
703        }
704        return e;
705    }
706
707    //
708    // Utility function to map PartitionManager.DisableAction to DisableAction
709    //
710    private static DEDisableAction mapDisable(
711        final PartitionManager.DisableAction disableAction)
712    {
713        DEDisableAction e;
714
715        switch (disableAction)
716        {
717        case LEAVE_CLUSTER:
718            e = DEDisableAction.LeaveCluster;
719            break;
720        default:
721            assert( disableAction == DisableAction.LEAVE_CLUSTER_FORCE );
722            e = DEDisableAction.LeaveClusterForce;
723            break;
724        }
725        return e;
726    }
727
728    //
729    // Utility function to map ReplicaNode.ReplicationType to DEReplicationType
730    //
731    static DEReplicationType mapReplicationType(
732        final ReplicaNode.ReplicationType replicationType)
733    {
734        DEReplicationType rt;
735
736        switch (replicationType)
737        {
738        case SYNCHRONOUS:
739            rt = DEReplicationType.DataSynchronous;
740            break;
741        default:
742            assert( replicationType ==
743                ReplicaNode.ReplicationType.ASYNCHRONOUS );
744            rt = DEReplicationType.DataAsynchronous;
745            break;
746        }
747        return rt;
748    }
749
750    //
751    // Utility function to map DEReplicationType to ReplicaNode.ReplicationType
752    //
753    static ReplicaNode.ReplicationType mapDEReplicationType(
754        final DEReplicationType rt)
755    {
756        ReplicaNode.ReplicationType replicationType;
757
758        switch (rt)
759        {
760        case DataSynchronous:
761            replicationType = ReplicaNode.ReplicationType.SYNCHRONOUS;
762            break;
763        default:
764            assert( rt == DEReplicationType.DataAsynchronous );
765            replicationType = ReplicaNode.ReplicationType.ASYNCHRONOUS;
766            break;
767        }
768        return replicationType;
769    }
770
771    //
772    // Utility function to map MapperProperties.Audit to DEMapperAudit
773    //
774    private static DEMapperAudit mapAudit(
775        final PartitionMapper.Properties.Audit audit)
776    {
777        DEMapperAudit e;
778
779        switch (audit)
780        {
781        case VERIFY_PARTIONING:
782            e = DEMapperAudit.MapperVerify;
783            break;
784        default:
785            assert( audit == PartitionMapper.Properties.Audit.IGNORE_PARTITIONING );
786            e = DEMapperAudit.MapperIgnore;
787            break;
788        }
789        return e;
790    }
791
792    //
793    // Never should be called
794    //
795    private PartitionManager()
796    {
797    }
798
799    //
800    // Private logger class
801    //
802    private static class DistributionLogger extends Logger
803    {
804        DistributionLogger()
805        {
806            super("com.kabira.platform.highavailability", null);
807            m_enabled = false;
808        }
809
810        public void setLevel(Level level)
811        {
812            super.setLevel(level);
813            if (level != null)
814            {
815                m_enabled = (level.intValue() <= Level.FINEST.intValue())
816                    ? true : false;
817                if (Transaction.isActive())
818                {
819                    debugTrace();
820                }
821                else
822                {
823                    new Transaction("DistributionLogger")
824                    {
825                        @Override
826                        protected void run() throws Rollback
827                        {
828                            debugTrace();
829                        }
830                    }.execute();
831                }
832            }
833        }
834
835        private boolean m_enabled;
836
837        private void debugTrace()
838        {
839                DEPartitionManager pm = new DEPartitionManager();
840                pm.debugTrace(m_enabled);
841        }
842
843        @Override
844        public String toString()
845        {
846            return "DistributionLogger";
847        }
848    }
849
850    //
851    // Create and register logger at start of world.
852    //
853    private static DistributionLogger    m_logger;
854
855    static
856    {
857        m_logger = new DistributionLogger();
858        LogManager lm = LogManager.getLogManager();
859        lm.addLogger(m_logger);
860    }
861}
862