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