001//
002// Name
003//  $RCSfile: PartitionManager.java,v $
004// 
005// Copyright
006//  Copyright 2010-2015 Cloud Software Group, Inc. ALL RIGHTS RESERVED. 
007//  Cloud Software Group, Inc. Confidential Information
008//
009// History
010//  $Revision: 1.1.2.57.2.1 $ $Date: 2015/04/30 19:57:55 $
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.PartitionStatus;
031import com.kabira.platform.disteng.DEReplicationType;
032import com.kabira.platform.disteng.DESparsePartitionAudit;
033import com.kabira.platform.disteng.DERemoteEnableAction;
034
035import java.lang.reflect.Field;
036import java.util.HashSet;
037import java.util.logging.LogManager;
038import java.util.logging.Logger;
039import java.util.logging.Level;
040
041/**
042 * The PartitionManager class. This is the main interface for managing
043 * partitions. 
044 */
045public final class PartitionManager 
046{
047    /**
048     * An enumeration of the possible actions that can be taken when
049     * enabling high availability for a node.
050     */
051    public enum EnableAction
052    {
053        /**
054         * Perform any object migrations needed to activate the
055         * partitions defined for this node. This option should be used
056         * for newly added nodes.
057         */
058        JOIN_CLUSTER,
059        /**
060         * Before migrating the partition data, remove any instances that
061         * exist on the local node. This option should be used for nodes
062         * where the local data doesn't need to be preserved.
063         */
064        JOIN_CLUSTER_PURGE,
065        /**
066         * For all partitions where the node joining the cluster is the
067         * current active node, access the remote node where the
068         * partition should be restored from, and compare the objects
069         * between the local node and remote node. If an object on the
070         * local node has been updated, or a new object created, push the
071         * update to the remote node. If the remote node has been
072         * updated, replicate the change to the local node. If a state
073         * conflict is detected, or a duplicate key is found, remove the
074         * local object and copy the remote node object to the local
075         * node.
076         * <p>
077         * For all partitions where the node joining the cluster is
078         * acting as a replica, the replica objects are deleted and
079         * instances migrated from the remote node.
080         * <p>
081         * Warning: This is performance hostile, and should only be done
082         * when resolving a split-brain scenario in a cluster.
083         * 
084         * @see Partition.Properties#restoreFromNode(String)
085         */
086        JOIN_CLUSTER_RESTORE
087    }
088
089    /**
090     * An enumeration of the possible actions that can be taken when
091     * disabling high availability for a node.
092     */
093    public enum DisableAction
094    {
095        /**
096         * Perform any object migrations needed to migrate
097         * partitions owned by this node to the first order
098         * replica node, and remove this node from all replica
099         * lists. Before the disable is executed, an audit is
100         * done to insure that no partitions will be moved to the
101         * UNAVAILABLE state.
102         */
103        LEAVE_CLUSTER,
104        /**
105         * Same as LEAVE_CLUSTER, but with audits disabled.
106         */
107        LEAVE_CLUSTER_FORCE,
108    }
109
110    /**
111     * Find all partitions on the local node.
112     * 
113     * @return Array of Partition instances.
114     */
115    public static final Partition [] getPartitions()
116    {
117        DEPartitionManager pm = new DEPartitionManager();
118        DEPartition [] dp = pm.getPartitions();
119
120        Partition [] pa = new Partition[dp.length];
121
122        for (int i = 0; i < dp.length; i++)
123        {
124            pa[i] = new Partition(
125                            dp[i].name,
126                            dp[i].nodes,
127                            dp[i].replicationTypeList,
128                            mapState(dp[i].state), 
129                            mapStatus(dp[i].status),
130                            dp[i].lastUpdated,
131                            dp[i].forceReplication,
132                            dp[i].objectChunks,
133                            dp[i].numThreads,
134                            mapDESparseAudit(dp[i].sparseAudit),
135                            mapDERemoteEnableAction(dp[i].remoteEnableAction),
136                            dp[i].broadcastUpdates);
137        }
138        return pa;
139    }
140
141    /**
142     * Find a partition by name.
143     * 
144     * @param partitionName Name of partition to find.
145     *
146     * @return Partition instance.
147     *
148     * @exception PartitionNotFound
149     *  The given partition doesn't exist.
150     * @exception IllegalArgumentException
151     *  The partitionName was empty.
152     */
153    public static final Partition getPartition(
154        final String partitionName) throws
155            IllegalArgumentException, PartitionNotFound
156    {
157        if (partitionName.isEmpty())
158        {
159            throw new IllegalArgumentException("partitionName cannot be empty");
160        }
161
162        return _getPartition(partitionName);
163    }
164
165    /**
166     * Get the partition an object is in.
167     * 
168     * @param obj Object to examine.
169     *
170     * @return Partition instance.
171     *
172     * @exception ManagedClassError
173     *  The object is not a Managed object.
174     * @exception PartitionNotFound
175     *  The given object isn't in a partition. This can happen if
176     *  object instances were created before a {@link PartitionMapper} was
177     *  installed for a managed class, or no mapper was ever defined.
178     */
179    public static final Partition getObjectPartition(
180        final Object obj) throws ManagedClassError, PartitionNotFound
181    {
182        checkManagedObject(obj);
183
184        DEPartitionManager pm = new DEPartitionManager();
185
186        String  partitionName = pm.getObjectPartition(obj);
187        if (partitionName.length() == 0)
188        {
189            throw new PartitionNotFound(
190                "No Partition exists for object '" + obj + "'");
191        }
192
193        return _getPartition(partitionName);
194    }
195
196    /**
197     * Move the instance from the current partition (if any) to
198     * the partition defined by the installed mapper for the object class.
199     * The interactions between installed mappers and partitions when an
200     * object is repartitioned is enumerated below:
201     * <ul>
202     * <li>
203     * If the object was not associated with a partition, and a mapper
204     * maps it to a new partition, the instance is replicated to all
205     * nodes defined for the new partition.
206     * </li>
207     * <li>
208     * If the object was mapped to a pre-existing partition, and a mapper
209     * maps it to a new partition, the instance is replicated to all
210     * nodes defined for the new partition. The instance is removed from
211     * all nodes that were defined in the old partition but are not
212     * defined with the new partition.
213     * </li>
214     * <li>
215     * If the object was associated with a partition, and a mapper no
216     * longer exists, the instance is removed from all nodes that that
217     * were defined in the old partition, and the instance is left in
218     * shared memory on the local node as un-partitioned.
219     * </li>
220     * <li>
221     * If the object was not associated with a partition, and a mapper
222     * does not exist, this operation does nothing.
223     * </li>
224     * </ul>
225     *
226     * @param obj Object to repartition.
227     *
228     * @exception ManagedClassError
229     *  The object is not a Managed object.
230     * @exception ResourceUnavailableException
231     *  An illegal partition name was returned from the mapper, or the
232     *  partition that is returned in not in the <b>ACTIVE</b> state.
233     */
234    public static final void repartitionInstance(final Object obj)
235          throws ManagedClassError
236    {
237        checkManagedObject(obj);
238
239        // First, clear the cached location in the Java proxy for this object.
240        //
241        // FIX THIS, DS: make this an undocumented native entry point
242        //               on ManagedObject?
243        try
244        {
245            Field localityField = obj.getClass().getField("$m_locality");
246            localityField.setByte(obj, (byte)0);
247        }
248        catch (NoSuchFieldException ex)
249        {
250            throw new ManagedClassError(
251                    "Clearing locality failed: " + ex.getMessage());
252        }
253        catch (SecurityException ex)
254        {
255            throw new ManagedClassError(
256                    "Clearing locality failed: " + ex.getMessage());
257        }
258        catch (IllegalArgumentException ex)
259        {
260            throw new ManagedClassError(
261                    "Clearing locality failed: " + ex.getMessage());
262        }
263        catch (IllegalAccessException ex)
264        {
265            throw new ManagedClassError(
266                    "Clearing locality failed: " + ex.getMessage());
267        }
268
269        DEPartitionManager pm = new DEPartitionManager();
270        pm.repartitionObject(obj);
271    }
272
273    /**
274     * Use {@link #definePartition(String, Partition.Properties, String, ReplicaNode [])}
275     * @param partitionName Name of partition to create.
276     * @param nodes An ordered list of nodes for the partition.
277     */
278    @Deprecated
279    public static final void definePartition(
280        final String partitionName,
281        final String [] nodes) throws IllegalArgumentException
282    {
283        if (partitionName.isEmpty())
284        {
285            throw new IllegalArgumentException("partitionName cannot be empty");
286        }
287        validateNodeList(nodes);
288
289        DEReplicationType [] rtl = new DEReplicationType[nodes.length];
290        for (int i = 0; i < rtl.length; i++)
291        {
292            rtl[i] = DEReplicationType.DataSynchronous;
293        }
294
295        DEProperties props = Partition.defaultDEProperties();
296
297        definePartition(partitionName, props, nodes, rtl);
298    }
299
300    /**
301     * Use {@link #definePartition(String, Partition.Properties, String, ReplicaNode [])}
302     * instead.
303     * @param partitionName Name of partition to create.
304     * @param restoreFromNode The remote node to use when restoring the
305     *  partition's objects.
306     * @param nodes An ordered list of nodes for the partition.
307     */
308    @Deprecated
309    public static final void definePartition(
310        final String partitionName,
311        final String restoreFromNode,
312        final String [] nodes) throws IllegalArgumentException, NotActiveNode
313    {
314        if (restoreFromNode.isEmpty())
315        {
316            throw new IllegalArgumentException(
317                "restoreFromNode cannot be empty");
318        }
319
320        if (partitionName.isEmpty())
321        {
322            throw new IllegalArgumentException("partitionName cannot be empty");
323        }
324        validateNodeList(nodes);
325
326        DEReplicationType [] rtl = new DEReplicationType[nodes.length];
327        for (int i = 0; i < rtl.length; i++)
328        {
329            rtl[i] = DEReplicationType.DataSynchronous;
330        }
331
332        DEProperties props = Partition.defaultDEProperties();
333        props.restoreFromNode = restoreFromNode;
334
335        definePartition(partitionName, props, nodes, rtl);
336    }
337
338    /**
339     * Define a partition.
340     * <p>
341     * This method defines a partition. This method can be called on any
342     * node in the cluster to define a new partition, or change the   
343     * definition of a pre-existing partition. Once all partitions have
344     * been defined for a node, the enablePartitions() method should be
345     * called to move the partitions to the active state. It is a runtime
346     * error to attempt to create or modify instances in a newly defined
347     * partition until the enablePartitions() method is called.
348     * <p>
349     * The activeNode is the node that will be the active node for the
350     * partition after it is enabled.
351     * <p>
352     * Optional partition properties can be used by passing in an
353     * instance of the {@link Partition.Properties} class. User defined
354     * properties such as forceReplication are saved and subsequently 
355     * used during failover processing. 
356     * <p>
357     * The replicas array contains an ordered list of replica nodes for
358     * the partition. A failure of the active node results in the
359     * partition automatically migrating to the the next entry in the
360     * replicas array. Objects are sent to each replica node based on
361     * the configuration in the ReplicaNode entry.
362     *
363     * @param partitionName Name of partition to create.
364     * @param partitionProperties Optional properties for the partition.
365     *  If null, the default property values are used.
366     * @param activeNode The active node for the partition
367     * @param replicas An ordered list of replica nodes for the partition.
368     *  Should be passed in as a null instance or a zero length array if
369     *  no replicas are desired.
370     *
371     * @exception IllegalArgumentException
372     *  The partitionName or activeNode was empty, or the replicas array
373     *  was invalid.
374     * @exception NotActiveNode
375     *  The current node is not the active node for the partition and 
376     *  restoreFromNode was defined in partitionProperties.
377     * @exception ResourceUnavailableException
378     *  The operation timed out waiting for the active node to become
379     *  available.
380     */
381    public static final void definePartition(
382        final String partitionName,
383        final Partition.Properties partitionProperties,
384        final String activeNode,
385        final ReplicaNode [] replicas)
386            throws IllegalArgumentException, NotActiveNode, ResourceUnavailableException
387    {
388        if (partitionName.isEmpty())
389        {
390            throw new IllegalArgumentException("partitionName cannot be empty");
391        }
392
393        if (activeNode.isEmpty())
394        {
395            throw new IllegalArgumentException("activeNode cannot be empty");
396        }
397
398        validateReplicaList(replicas);
399
400        int nodeLength = (replicas == null) ? 1 : replicas.length + 1;
401
402        String [] nodes = new String[nodeLength];
403
404        nodes[0] = activeNode;
405        if (replicas != null)
406        {
407            for (int i = 0; i < replicas.length; i++)
408            {
409                assert( i + 1 < nodeLength );
410                nodes[i + 1] = replicas[i].nodeName;
411            }
412        }
413        validateNodeList(nodes);
414
415        DEReplicationType [] rtl = new DEReplicationType[nodes.length];
416        rtl[0] = DEReplicationType.DataSynchronous;
417        if (replicas != null)
418        {
419            for (int i = 0; i < replicas.length; i++)
420            {
421                rtl[i + 1] = mapReplicationType(replicas[i].replicationType);
422            }
423        }
424
425        DEProperties props = Partition.copyDEProperties(partitionProperties);
426
427        definePartition(partitionName, props, nodes, rtl);
428    }
429
430    /**
431     * Associate a mapper to a given managed class.
432     *
433     * This method uses {@link PartitionMapper.Properties.Audit#IGNORE_PARTITIONING}
434     * for audit.
435     * 
436     * @param managedClass Name of a Managed object.
437     * @param partitionMapper User defined PartitionMapper instance.
438     *
439     * @exception ManagedClassError
440     *  The managedClass is not a Managed object, or has a non-abstract
441     *  parent class with a key defined but no mapper installed.
442     *
443     * @see PartitionManager#setMapper(Class, PartitionMapper, PartitionMapper.Properties)
444     */
445    public static final void setMapper(
446        final Class<?> managedClass,
447        final PartitionMapper partitionMapper) throws ManagedClassError
448    {
449        setMapper(managedClass, partitionMapper, null);
450    }
451
452    /**
453     * Associate a mapper to a given managed class.
454     * <p>
455     * This method is used to associate a user defined PartitionMapper
456     * instance with a managed class. All managed types that are replicated 
457     * must have a PartitionMapper associated with the class. This method must
458     * be called on all nodes that has the partition definition.
459     * <p>
460     * The same PartitionMapper instance can be associated with multiple
461     * classes. If a PartitionMapper is already associated with a class,
462     * the new value overwrites the previous one.
463     * <p>
464     * Applications should only delete a PartitionMapper instance after a
465     * new instance is installed. If a PartitionMapper instance is
466     * deleted, any subsequent object creates for the class will throw a
467     * runtime exception.
468     * <p>
469     * Mapper instances are inherited by all classes that extend the
470     * managed class. If the managed class extends another class, that
471     * class must not have any keys defined.
472     * <p>
473     * If auditing is enabled in the mapperProperties instance, this routine
474     * will verify that no unpartitioned instances of the given class
475     * already exist in shared memory. This can happen if instances are
476     * created before a mapper is installed.
477     * 
478     * @param managedClass Managed class.
479     * @param partitionMapper User defined PartitionMapper instance.
480     * @param mapperProperties Optional mapper properties. If null,
481     * {@link PartitionMapper.Properties.Audit#IGNORE_PARTITIONING} is used.
482     *
483     * @return Old mapper instance associated with managedClass, or null if
484     *  no mapper was set.
485     *
486     * @exception ManagedClassError
487     *  The managedClass is not a Managed object, or has a non-abstract
488     *  parent class with a key defined but no mapper installed.
489     */
490    public static final PartitionMapper setMapper(
491        final Class<?> managedClass,
492        final PartitionMapper partitionMapper,
493        final PartitionMapper.Properties mapperProperties)
494            throws ManagedClassError
495    {
496        try
497        {
498            checkManagedClass(managedClass);
499            return setMapper(managedClass.getName(),
500                partitionMapper, mapperProperties);
501        }
502        catch (ResourceUnavailableException ex)
503        {
504            //
505            // FIX THIS: Probably  untestable because of the checkManagedClass()
506            //
507            throw new ManagedClassError(ex.getMessage());
508        }
509    }
510
511    /**
512     * Associate a mapper to a given shared memory type.
513     * <p>
514     * This method is used to associate a user defined PartitionMapper
515     * instance with shared memory type. All types that are replicated 
516     * must have a PartitionMapper associated with the class. This method must
517     * be called on all nodes that has the partition definition.
518     * <p>
519     * The same PartitionMapper instance can be associated with multiple
520     * type. If a PartitionMapper is already associated with a type,
521     * the new value overwrites the previous one.
522     * <p>
523     * Applications should only delete a PartitionMapper instance after a
524     * new instance is installed. If a PartitionMapper instance is
525     * deleted, any subsequent object creates for the type will throw a
526     * runtime exception.
527     * <p>
528     * Mapper instances are inherited by all subtypes. If the type
529     * extends another type, that class must not have any keys defined.
530     * <p>
531     * If auditing is enabled in the mapperProperties instance, this routine
532     * will verify that no unpartitioned instances of the given type
533     * already exist in shared memory. This can happen if instances are
534     * created before a mapper is installed.
535     * 
536     * @param typeName Name of a shared memory type.
537     * @param partitionMapper User defined PartitionMapper instance.
538     * @param mapperProperties Optional mapper properties. If null,
539     * {@link PartitionMapper.Properties.Audit#IGNORE_PARTITIONING} is used.
540     *
541     * @return Old mapper instance associated with managedClass, or null if
542     *  no mapper was set.
543     *
544     * @exception ResourceUnavailableException
545     *  The typeName is not a shared memory type, or has a non-abstract
546     *  parent class with a key defined but no mapper installed.
547     */
548    public static final PartitionMapper setMapper(
549        final String typeName,
550        final PartitionMapper partitionMapper,
551        final PartitionMapper.Properties mapperProperties)
552            throws ResourceUnavailableException
553    {
554        if (partitionMapper == null)
555        {
556            throw new NullPointerException("partitionMapper cannot be null");
557        }
558        DEPartitionManager pm = new DEPartitionManager();
559        DEMapperAudit audit = (mapperProperties == null)
560            ? DEMapperAudit.MapperIgnore
561            : mapAudit(mapperProperties.getAudit());
562
563        return (PartitionMapper)pm.setMapperAudit(
564                        typeName, partitionMapper, audit);
565    }
566
567    /**
568     * Clear a mapper for a given managed class.
569     * <p>
570     * This method is used to remove a user defined PartitionMapper
571     * instance for a managed class. This method must be called on all
572     * nodes where the mapper was previously defined.
573     * <p>
574     * If no PartitionMapper is associated with a class, this operation
575     * is a noop.
576     *
577     * @param managedClass Managed class.
578     *
579     * @exception ManagedClassError
580     *  The managedClass is not a Managed object.
581     *
582     * @see PartitionManager#setMapper(Class, PartitionMapper, PartitionMapper.Properties)
583     */
584    public static final void clearMapper(
585        final Class<?> managedClass) throws ManagedClassError
586    {
587        try
588        {
589            clearMapper(managedClass.getName());
590        }
591        catch (ResourceUnavailableException ex)
592        {
593            throw new ManagedClassError(ex.getMessage());
594        }
595    }
596
597    /**
598     * Clear a mapper for a given type.
599     * <p>
600     * This method is used to remove a user defined PartitionMapper
601     * instance for a shared memory type. This method must be called on all
602     * nodes where the mapper was previously defined.
603     * <p>
604     * If no PartitionMapper is associated with a class, this operation
605     * is a noop.
606     *
607     * @param typeName Name of a Managed object.
608     *
609     * @exception ResourceUnavailableException
610     *  The typeName does not exist in shared memory.
611     *
612     * @see PartitionManager#setMapper(String, PartitionMapper, PartitionMapper.Properties)
613     */
614    public static final void clearMapper(
615        final String typeName) throws ResourceUnavailableException
616    {
617        DEPartitionManager pm = new DEPartitionManager();
618        pm.setMapperAudit(typeName, null, DEMapperAudit.MapperIgnore);
619    }
620
621    /**
622     * Use {@link com.kabira.platform.ManagedObject#isPartitioned}
623      * @param managedClass Class to check.
624      * @return true if class is partitioned.
625     */
626    @Deprecated
627    public static final boolean isPartitioned(final Class<?> managedClass)
628    {
629        return ManagedObject.isPartitioned(managedClass);
630    }
631
632    /**
633     * Enable high availability for the all partitions defined on the node.
634     * <p>
635     * This method is used to enable all partitions that have been
636     * defined for this node. This method should be called each time new
637     * partitions are defined.
638     * This method blocks until all required object migration
639     * is complete.
640     *
641     * @param enableAction The action to take when enabling partitions.
642     *
643     * @exception NodeMismatch
644     *  The partition does not have the local node in the node list, and the
645     *  active node for the partition has a different node list.
646     * @exception ResourceUnavailableException
647     *  The minimum number of nodes needed to establish a quorum has not been
648     *  seen.
649     */
650    public static final void enablePartitions(
651        final EnableAction enableAction)
652            throws NodeMismatch, ResourceUnavailableException
653    {
654        DEPartitionManager pm = new DEPartitionManager();
655        try
656        {
657            pm.enablePartitions(mapEnable(enableAction));
658        }
659        catch (DENodeMismatch ex)
660        {
661            throw new NodeMismatch(ex.errMsg);
662        }
663    }
664
665    /**
666     * Disable high availability for the all partitions defined on the node.
667     * <p>
668     * This method is used to remove the local node from the node list of
669     * all partitions that have been defined for this node. If called
670     * multiple times, the additional calls have no effect.
671     *
672     * @param disableAction  The action to take when disabling partitions.
673     *
674     * @exception ResourceUnavailableException
675     *  One or more partitions would be in the UNAVAILABLE state after the
676     *  disable executes, not thrown if LEAVE_CLUSTER_FORCE is used.
677     */
678    public static final void disablePartitions(
679        final DisableAction disableAction) throws ResourceUnavailableException
680    {
681        DEPartitionManager pm = new DEPartitionManager();
682        pm.disablePartitions(mapDisable(disableAction));
683    }
684
685    /**
686     * Wait for a remote node to become available.
687     *
688     * @param remoteNode name of remote node to wait for.
689     *
690     * @exception ResourceUnavailableException
691     *  The operation timed out waiting for the remote node to become
692     *  available.
693     */
694    public static final void waitForNode(
695        final String remoteNode) throws ResourceUnavailableException
696    {
697        DEPartitionManager pm = new DEPartitionManager();
698        pm.waitForNode(remoteNode);
699    }
700
701    /**
702     * Create local copies of all partition defined by the remote node.
703     * <p>
704     * After execution of this method completes, all partitions defined
705     * on the remote node will exist on the local node, and will be
706     * accessible using the {@link #getPartition(String)} method.
707     * 
708     * @param remoteNode name of remote node to access.
709     *
710     * @exception ResourceUnavailableException
711     *  The operation timed out waiting for the remote node to become
712     *  available.
713     */
714    public static final void getRemotePartitions(
715        final String remoteNode) throws ResourceUnavailableException
716    {
717        DEPartitionManager pm = new DEPartitionManager();
718        pm.getRemotePartitions(remoteNode);
719    }
720
721    //
722    // This routine performs initialization of the DistributionLogger
723    // logger, and runtime resources used in the highavailability
724    // package. It normally is called from a static initializer defined
725    // in this class, but can also be called from ManagedObject via
726    // reflection.
727    //
728    // Note: We assume caller synchronizes this call.
729    //
730    private static final void initialize()
731    {
732        if (m_logger == null)
733        {
734            m_logger = new DistributionLogger();
735            LogManager lm = LogManager.getLogManager();
736            lm.addLogger(m_logger);
737            ManagedObject.setDistributedQuery(new DistributedQuery());
738        }
739    }
740
741    //
742    // Package private methods. These don't bother with handling
743    // PartitionNotFound exceptions, since they can only be called after
744    // getPartition() was executed, and we never delete partitions.
745    //
746
747    static final void updatePartition(
748        final String existingPartition,
749        DEProperties props) throws NodeMismatch, NotActiveNode
750    {
751        DEPartitionManager pm = new DEPartitionManager();
752        try
753        {
754            pm.updatePartition(existingPartition, props);
755        }
756        catch (DENodeMismatch ex)
757        {
758            throw new NodeMismatch(ex.errMsg);
759        }
760        catch (DENotActiveNode ex)
761        {
762            throw new NotActiveNode(ex.errMsg);
763        }
764    }
765
766    static final void migratePartition(
767        final String partitionName,
768        DEProperties props,
769        final String [] nodes,
770        DEReplicationType [] rtl) throws NotActiveNode
771    {
772        DEPartitionManager pm = new DEPartitionManager();
773        try
774        {
775            pm.migratePartition(partitionName, props, nodes, rtl);
776        }
777        catch (DENotActiveNode ex)
778        {
779            throw new NotActiveNode(ex.errMsg);
780        }
781    }
782
783    static final void setNotifier(
784        final String partitionName,
785        final PartitionNotifier partitionNotifier) throws PartitionNotFound
786    {
787        Notifier notifier = new Notifier(partitionNotifier);
788        DEPartitionManager pm = new DEPartitionManager();
789        pm.setNotifier(partitionName, notifier);
790    }
791
792    //
793    // Validate the replica list. We just check for null replicas here, the
794    // rest of the checks are done in validateNodeList()
795    //
796    static void validateReplicaList(
797        final ReplicaNode [] replicas) throws IllegalArgumentException
798    {
799        if (replicas != null)
800        {
801            for (int i = 0; i < replicas.length; i++)
802            {
803                ReplicaNode r = replicas[i];
804                if (r == null)
805                {
806                    throw new IllegalArgumentException(
807                        "Replica entry at index " + i + " cannot be empty");
808                }
809            }
810        }
811    }
812
813    //
814    // Validate the node list
815    //
816    static void validateNodeList(
817        final String [] nodes) throws IllegalArgumentException
818    {
819        HashSet<String>     nodeSet = new HashSet<String>();
820        if (nodes.length == 0)
821        {
822            throw new IllegalArgumentException(
823                "The node array must have at least one entry");
824        }
825        for (int i = 0; i < nodes.length; i++)
826        {
827            String node = nodes[i];
828            if (node == null || node.isEmpty())
829            {
830                throw new IllegalArgumentException("Node entry at index " +
831                    i + " cannot be empty");
832            }
833            if (nodeSet.contains(node))
834            {
835                throw new IllegalArgumentException("Node entry " + node +
836                    " at index " + i + " is a duplicate. " +
837                    "All nodes must be unique");
838            }
839            nodeSet.add(node);
840        }
841    }
842
843    //
844    // Internal getPartition() method
845    //
846    private static Partition _getPartition(
847        final String partitionName) throws PartitionNotFound
848    {
849        assert( !partitionName.isEmpty() );
850
851        DEPartitionManager pm = new DEPartitionManager();
852
853        try
854        {
855            DEPartition p = pm.getPartition(partitionName);
856            return new Partition(
857                        p.name,
858                        p.nodes,
859                        p.replicationTypeList,
860                        mapState(p.state),
861                        mapStatus(p.status),
862                        p.lastUpdated,
863                        p.forceReplication,
864                        p.objectChunks,
865                        p.numThreads,
866                        mapDESparseAudit(p.sparseAudit),
867                        mapDERemoteEnableAction(p.remoteEnableAction),
868                        p.broadcastUpdates);
869        }
870        catch (DEPartitionNotFound ex)
871        {
872            throw new PartitionNotFound(ex.errMsg);
873        }
874    }
875
876    //
877    // Wrapper class to map notifiers.
878    //
879    private static class Notifier extends DEPartitionNotifier
880    {
881        public void stateChange(
882                final String partitionName,
883                final PartitionState oldState,
884                final PartitionState newState)
885        {
886            if (ManagedObject.isEmpty(m_partitionNotifier))
887            {
888                ManagedObject.delete(this);
889            }
890            else
891            {
892                m_partitionNotifier.stateChange(
893                    partitionName,
894                    mapState(oldState),
895                    mapState(newState));
896            }
897        }
898
899        Notifier(PartitionNotifier partitionNotifier)
900        {
901            m_partitionNotifier = partitionNotifier;
902        }
903
904        PartitionNotifier   m_partitionNotifier;
905    }
906
907    //
908    // Wrapper method to map definePartition exceptions.
909    //
910    private static void definePartition(
911        final String partitionName,
912        final DEProperties props,
913        final String [] nodes,
914        DEReplicationType [] rtl)
915            throws NotActiveNode
916    {
917        DEPartitionManager pm = new DEPartitionManager();
918
919        try
920        {
921            pm.definePartition(partitionName, props, nodes, rtl);
922        }
923        catch (DENotActiveNode ex)
924        {
925            throw new NotActiveNode(ex.errMsg);
926        }
927    }
928
929    //
930    // Utility function to map PartitionState to Partition.State
931    //
932    private static Partition.State mapState(PartitionState state)
933    {
934        Partition.State s;
935
936        switch (state)
937        {
938        case PartitionInitial:
939            s = Partition.State.INITIAL;
940            break;
941        case PartitionActive:
942            s = Partition.State.ACTIVE;
943            break;
944        case PartitionUpdating:
945            s = Partition.State.UPDATING;
946            break;
947        case PartitionMigrating:
948            s = Partition.State.MIGRATING;
949            break;
950        case PartitionReplicating:
951            s = Partition.State.REPLICATING;
952            break;
953        default:
954            assert( state == PartitionState.PartitionUnavailable );
955            s = Partition.State.UNAVAILABLE;
956            break;
957        }
958        return s;
959    }
960
961    //
962    // Utility function to map PartitionStatus to Partition.Status
963    //
964    private static Partition.Status mapStatus(PartitionStatus status)
965    {
966        Partition.Status s;
967
968        switch (status)
969        {
970        case LocalDisabled:
971            s = Partition.Status.LOCAL_DISABLED;
972            break;
973        case LocalDefined:
974            s = Partition.Status.LOCAL_DEFINED;
975            break;
976        case LocalEnabled:
977            s = Partition.Status.LOCAL_ENABLED;
978            break;
979        case RemoteDefined:
980            s = Partition.Status.REMOTE_DEFINED;
981            break;
982        default:
983            assert( status == PartitionStatus.RemoteEnabled );
984            s = Partition.Status.REMOTE_ENABLED;
985            break;
986        }
987        return s;
988    }
989
990    //
991    // Utility function to map PartitionManager.EnableAction to EnableAction
992    //
993    static DEEnableAction mapEnable(
994        final PartitionManager.EnableAction enableAction)
995    {
996        DEEnableAction e;
997
998        switch (enableAction)
999        {
1000        case JOIN_CLUSTER:
1001            e = DEEnableAction.JoinCluster;
1002            break;
1003        case JOIN_CLUSTER_PURGE:
1004            e = DEEnableAction.JoinClusterPurge;
1005            break;
1006        default:
1007            assert( enableAction == EnableAction.JOIN_CLUSTER_RESTORE );
1008            e = DEEnableAction.JoinClusterRestore;
1009            break;
1010        }
1011        return e;
1012    }
1013
1014    //
1015    // Utility function to map PartitionManager.DisableAction to DisableAction
1016    //
1017    static DEDisableAction mapDisable(
1018        final PartitionManager.DisableAction disableAction)
1019    {
1020        DEDisableAction e;
1021
1022        switch (disableAction)
1023        {
1024        case LEAVE_CLUSTER:
1025            e = DEDisableAction.LeaveCluster;
1026            break;
1027        default:
1028            assert( disableAction == DisableAction.LEAVE_CLUSTER_FORCE );
1029            e = DEDisableAction.LeaveClusterForce;
1030            break;
1031        }
1032        return e;
1033    }
1034
1035    //
1036    // Utility function to map ReplicaNode.ReplicationType to DEReplicationType
1037    //
1038    static DEReplicationType mapReplicationType(
1039        final ReplicaNode.ReplicationType replicationType)
1040    {
1041        DEReplicationType rt;
1042
1043        switch (replicationType)
1044        {
1045        case SYNCHRONOUS:
1046            rt = DEReplicationType.DataSynchronous;
1047            break;
1048        default:
1049            assert( replicationType ==
1050                ReplicaNode.ReplicationType.ASYNCHRONOUS );
1051            rt = DEReplicationType.DataAsynchronous;
1052            break;
1053        }
1054        return rt;
1055    }
1056
1057    //
1058    // Utility function to map DEReplicationType to ReplicaNode.ReplicationType
1059    //
1060    static ReplicaNode.ReplicationType mapDEReplicationType(
1061        final DEReplicationType rt)
1062    {
1063        ReplicaNode.ReplicationType replicationType;
1064
1065        switch (rt)
1066        {
1067        case DataSynchronous:
1068            replicationType = ReplicaNode.ReplicationType.SYNCHRONOUS;
1069            break;
1070        default:
1071            assert( rt == DEReplicationType.DataAsynchronous );
1072            replicationType = ReplicaNode.ReplicationType.ASYNCHRONOUS;
1073            break;
1074        }
1075        return replicationType;
1076    }
1077
1078    //
1079    // Utility function to map SparseAudit to DESparsePartitionAudit
1080    //
1081    static DESparsePartitionAudit mapSparseAudit(
1082        final Partition.Properties.SparseAudit sparseAudit)
1083    {
1084        DESparsePartitionAudit spa;
1085
1086        switch (sparseAudit)
1087        {
1088        case VERIFY_NODE_LIST:
1089            spa = DESparsePartitionAudit.VerifyNodeList;
1090            break;
1091        default:
1092            assert( sparseAudit ==
1093                Partition.Properties.SparseAudit.IGNORE_NODE_LIST );
1094            spa = DESparsePartitionAudit.IgnoreNodeList;
1095            break;
1096        }
1097        return spa;
1098    }
1099
1100    //
1101    // Utility function to map DESparsePartitionAudit to SparseAudit
1102    //
1103    static Partition.Properties.SparseAudit mapDESparseAudit(
1104        final DESparsePartitionAudit sparseAudit)
1105    {
1106        Partition.Properties.SparseAudit spa;
1107
1108        switch (sparseAudit)
1109        {
1110        case VerifyNodeList:
1111            spa = Partition.Properties.SparseAudit.VERIFY_NODE_LIST;
1112            break;
1113        default:
1114            assert( sparseAudit == DESparsePartitionAudit.IgnoreNodeList );
1115            spa = Partition.Properties.SparseAudit.IGNORE_NODE_LIST;
1116            break;
1117        }
1118        return spa;
1119    }
1120
1121    //
1122    // Utility function to map RemoteEnableAction to DERemoteEnableAction
1123    //
1124    static DERemoteEnableAction mapRemoteEnableAction(
1125        final Partition.Properties.RemoteEnableAction remoteEnableAction)
1126    {
1127        DERemoteEnableAction rea;
1128
1129        switch (remoteEnableAction)
1130        {
1131        case ENABLE_PARTITION:
1132            rea = DERemoteEnableAction.EnablePartition;
1133            break;
1134        default:
1135            assert( remoteEnableAction ==
1136                Partition.Properties.RemoteEnableAction.LEAVE_DISABLED );
1137            rea = DERemoteEnableAction.LeaveDisabled;
1138            break;
1139        }
1140        return rea;
1141    }
1142
1143    //
1144    // Utility function to map DERemoteEnableAction to RemoteEnableAction
1145    //
1146    static Partition.Properties.RemoteEnableAction mapDERemoteEnableAction(
1147        final DERemoteEnableAction remoteEnableAction)
1148    {
1149        Partition.Properties.RemoteEnableAction rea;
1150
1151        switch (remoteEnableAction)
1152        {
1153        case EnablePartition:
1154            rea = Partition.Properties.RemoteEnableAction.ENABLE_PARTITION;
1155            break;
1156        default:
1157            assert( remoteEnableAction == DERemoteEnableAction.LeaveDisabled );
1158            rea = Partition.Properties.RemoteEnableAction.LEAVE_DISABLED;
1159            break;
1160        }
1161        return rea;
1162    }
1163
1164    //
1165    // Utility function to map MapperProperties.Audit to DEMapperAudit
1166    //
1167    private static DEMapperAudit mapAudit(
1168        final PartitionMapper.Properties.Audit audit)
1169    {
1170        DEMapperAudit e;
1171
1172        switch (audit)
1173        {
1174        case VERIFY_PARTITIONING:
1175        case VERIFY_PARTIONING:
1176            e = DEMapperAudit.MapperVerify;
1177            break;
1178        case VERIFY_DEFER_INSTALL:
1179            e = DEMapperAudit.MapperDefer;
1180            break;
1181        default:
1182            assert( audit == PartitionMapper.Properties.Audit.IGNORE_PARTITIONING );
1183            e = DEMapperAudit.MapperIgnore;
1184            break;
1185        }
1186        return e;
1187    }
1188
1189    //
1190    // Validates a given object is managed.
1191    //
1192    private static void checkManagedObject(Object obj)
1193    {
1194        if (!ManagedObject.isManaged(obj))
1195        {
1196            throw new ManagedClassError("class " + obj.getClass().getName() +
1197                " is not a Managed class.");
1198        }
1199    }
1200
1201    //
1202    // Validates a given class is managed.
1203    //
1204    private static void checkManagedClass(Class<?> klass)
1205    {
1206        if (!ManagedObject.isManagedClass(klass))
1207        {
1208            throw new ManagedClassError("class " + klass.getName() +
1209                " is not a Managed class.");
1210        }
1211    }
1212
1213    //
1214    // Never should be called
1215    //
1216    private PartitionManager()
1217    {
1218    }
1219
1220    //
1221    // Private logger class
1222    //
1223    private static class DistributionLogger extends Logger
1224    {
1225        DistributionLogger()
1226        {
1227            super("com.kabira.platform.highavailability", null);
1228            m_enabled = false;
1229        }
1230
1231        public void setLevel(Level level)
1232        {
1233            super.setLevel(level);
1234            if (level != null)
1235            {
1236                m_enabled = (level.intValue() <= Level.FINEST.intValue())
1237                    ? true : false;
1238                if (Transaction.isActive())
1239                {
1240                    debugTrace();
1241                }
1242                else
1243                {
1244                    new Transaction("DistributionLogger")
1245                    {
1246                        @Override
1247                        protected void run() throws Rollback
1248                        {
1249                            debugTrace();
1250                        }
1251                    }.execute();
1252                }
1253            }
1254        }
1255
1256        private boolean m_enabled;
1257
1258        private void debugTrace()
1259        {
1260                DEPartitionManager pm = new DEPartitionManager();
1261                pm.debugTrace(m_enabled);
1262        }
1263
1264        @Override
1265        public String toString()
1266        {
1267            StringBuilder sb =
1268                new StringBuilder("DistributionLogger: debug tracing is ");
1269            sb.append(m_enabled ? "enabled" : "disabled");
1270            return sb.toString();
1271        }
1272    }
1273
1274    private static DistributionLogger    m_logger;
1275
1276    //
1277    // Create and register logger and DistributedQuery instance at
1278    // start of world.
1279    //
1280    static
1281    {
1282        initialize();
1283    }
1284}
1285