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