1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002, 2014 Oracle and/or its affiliates.  All rights reserved.
5  *
6  */
7 
8 package com.sleepycat.je.rep.impl;
9 
10 import java.io.Serializable;
11 import java.net.InetSocketAddress;
12 import java.util.HashMap;
13 import java.util.HashSet;
14 import java.util.Iterator;
15 import java.util.Map;
16 import java.util.Set;
17 import java.util.UUID;
18 
19 import com.sleepycat.bind.tuple.TupleBinding;
20 import com.sleepycat.bind.tuple.TupleInput;
21 import com.sleepycat.bind.tuple.TupleOutput;
22 import com.sleepycat.je.DatabaseException;
23 import com.sleepycat.je.EnvironmentFailureException;
24 import com.sleepycat.je.JEVersion;
25 import com.sleepycat.je.rep.MemberNotFoundException;
26 import com.sleepycat.je.rep.impl.RepGroupDB.NodeBinding;
27 import com.sleepycat.je.utilint.VLSN;
28 
29 /**
30  * Represents a snapshot of the Replication Group as a whole. Note that
31  * membership associated with a group is dynamic and its constituents can
32  * change at any time. It's useful to keep in mind that due to the distributed
33  * nature of the Replication Group all the nodes in a replication group may not
34  * have the same consistent picture of the replication group at a single point
35  * in time, but will converge to become consistent eventually.
36  */
37 public class RepGroupImpl {
38 
39     /** The latest supported format version. */
40     public static final int MAX_FORMAT_VERSION = 3;
41 
42     /**
43      * Format version introduced in JE 6.0.1 that records a node's most recent
44      * JE version, and the minimum JE version required to join the group.
45      */
46     public static final int FORMAT_VERSION_3 = 3;
47 
48     /**
49      * The latest format version that is compatible with JE 6.0.0 and earlier
50      * versions.
51      */
52     public static final int FORMAT_VERSION_2 = 2;
53 
54     /** The initial format version for newly created RepGroupImpl instances. */
55     public static final int INITIAL_FORMAT_VERSION = 3;
56 
57     /** The oldest supported format version. */
58     static final int MIN_FORMAT_VERSION = 2;
59 
60     /** The first JE version that supports FORMAT_VERSION_3. */
61     public static final JEVersion FORMAT_VERSION_3_JE_VERSION =
62         new JEVersion("6.0.1");
63 
64     /**
65      * The first JE version that supports the oldest supported format version.
66      */
67     public static final JEVersion MIN_FORMAT_VERSION_JE_VERSION =
68         new JEVersion("5.0.0");
69 
70     /** The initial change version. */
71     private final static int CHANGE_VERSION_START = 0;
72 
73     /*
74      * The special UUID associated with a group, when the group UUID is unknown
75      * because a node is still in the process of joining the group. This value
76      * cannot be created by UUID.randomUUID
77      */
78     private final static UUID UNKNOWN_UUID = new UUID(0, 0);
79 
80     /**
81      * The maximum number of secondary nodes that can join the group at the
82      * same time time.  This number of secondary node IDs will be reserved at
83      * the top of the node ID range.
84      */
85     public static final int MAX_SECONDARY_NODES = 1024;
86 
87     /** The first node ID for persistent nodes. */
88     private static final int NODE_SEQUENCE_START = 0;
89 
90     /** The maximum node ID for persistent nodes. */
91     private static final int NODE_SEQUENCE_MAX =
92         Integer.MAX_VALUE - MAX_SECONDARY_NODES;
93 
94     /** Returns true if the node is electable. */
95     private static final Predicate ELECTABLE_PREDICATE = new Predicate() {
96         @Override
97         boolean include(final RepNodeImpl n) {
98             return n.getType().isElectable();
99         }
100     };
101 
102     /** Returns true if the node is a monitor. */
103     private static final Predicate MONITOR_PREDICATE = new Predicate() {
104         @Override
105         boolean include(final RepNodeImpl n) {
106             return n.getType().isMonitor();
107         }
108     };
109 
110     /** Returns true if the node is secondary. */
111     private static final Predicate SECONDARY_PREDICATE = new Predicate() {
112         @Override
113         boolean include(final RepNodeImpl n) {
114             return n.getType().isSecondary();
115         }
116     };
117 
118     /* The name of the Replication Group. */
119     private final String groupName;
120 
121     /*
122      * The universally unique UUID associated with the replicated environment.
123      */
124     private UUID uuid;
125 
126     /*
127      * The version number associated with this group's format in the database.
128      */
129     private volatile int formatVersion;
130 
131     /*
132      * Tracks the change version level. It's updated with every change to the
133      * member set in the membership database.
134      */
135     private int changeVersion = 0;
136 
137     /*
138      * The most recently assigned node ID for persistent nodes.  Node IDs for
139      * persistent nodes are never reused.
140      */
141     private int nodeIdSequence;
142 
143     /*
144      * The following maps represent the set of nodes in the group indexed in
145      * two different ways: by user-defined node name and by internal id. Note
146      * that both maps contain nodes that are no longer members of the group.
147      *
148      * All access to nodesById and nodesByName should be synchronized on
149      * nodesById, to avoid ConcurrentModificationException and to provide
150      * consistent results for both maps.
151      */
152 
153     /* All the nodes that form the replication group, indexed by Id. */
154     private final Map<Integer, RepNodeImpl> nodesById =
155         new HashMap<Integer, RepNodeImpl>();
156 
157     /*
158      * All the nodes that form the replication group, indexed by node name.
159      * This map is used exclusively for efficient lookups by name. The map
160      * nodesById does all the heavy lifting.
161      */
162     private final Map<String, RepNodeImpl> nodesByName =
163         new HashMap<String, RepNodeImpl>();
164 
165     /** The minimum JE version required for nodes to join the group. */
166     private volatile JEVersion minJEVersion = MIN_FORMAT_VERSION_JE_VERSION;
167 
168     /**
169      * Constructor to create a new empty repGroup, typically as part of
170      * environment initialization.
171      *
172      * @param groupName the group name
173      * @param currentJEVersion if non-null, override the current JE version,
174      * for testing
175      */
RepGroupImpl(String groupName, JEVersion currentJEVersion)176     public RepGroupImpl(String groupName, JEVersion currentJEVersion) {
177         this(groupName, false, currentJEVersion);
178     }
179 
180     /**
181      * Constructor to create a group and specify if the group's UUID should be
182      * unknown or generated randomly.
183      */
RepGroupImpl(String groupName, boolean unknownUUID, JEVersion currentJEVersion)184     RepGroupImpl(String groupName,
185                  boolean unknownUUID,
186                  JEVersion currentJEVersion) {
187         this(groupName,
188              unknownUUID ? UNKNOWN_UUID : UUID.randomUUID(),
189              getCurrentFormatVersion(currentJEVersion));
190     }
191 
192     /** Get the current format version, supporting a test override. */
getCurrentFormatVersion( final JEVersion currentJEVersion)193     private static int getCurrentFormatVersion(
194         final JEVersion currentJEVersion) {
195 
196         return (currentJEVersion == null) ?
197             MAX_FORMAT_VERSION :
198             getMaxFormatVersion(currentJEVersion);
199     }
200 
201     /**
202      * Constructor to create a group and specify the group's UUID and format
203      * version.
204      */
RepGroupImpl(String groupName, UUID uuid, int formatVersion)205     public RepGroupImpl(String groupName, UUID uuid, int formatVersion) {
206         this(groupName,
207              uuid,
208              formatVersion,
209              CHANGE_VERSION_START,
210              NODE_SEQUENCE_START,
211              ((formatVersion < FORMAT_VERSION_3) ?
212               MIN_FORMAT_VERSION_JE_VERSION :
213               FORMAT_VERSION_3_JE_VERSION));
214     }
215 
216     /**
217      * Constructor used to recreate an existing RepGroup, typically as part of
218      * a deserialization operation.
219      *
220      * @param groupName
221      * @param uuid
222      * @param formatVersion
223      * @param changeVersion
224      * @param minJEVersion
225      */
RepGroupImpl(String groupName, UUID uuid, int formatVersion, int changeVersion, int nodeIdSequence, JEVersion minJEVersion)226     public RepGroupImpl(String groupName,
227                         UUID uuid,
228                         int formatVersion,
229                         int changeVersion,
230                         int nodeIdSequence,
231                         JEVersion minJEVersion) {
232         this.groupName = groupName;
233         this.uuid = uuid;
234         this.formatVersion = formatVersion;
235         this.changeVersion = changeVersion;
236         setNodeIdSequence(nodeIdSequence);
237         this.minJEVersion = minJEVersion;
238 
239         if (formatVersion < MIN_FORMAT_VERSION ||
240             formatVersion > MAX_FORMAT_VERSION) {
241             throw new IllegalStateException(
242                 "Expected membership database format version between: " +
243                 MIN_FORMAT_VERSION + " and " + MAX_FORMAT_VERSION +
244                 ", encountered unsupported version: " + formatVersion);
245         }
246         if (minJEVersion == null) {
247             throw new IllegalArgumentException(
248                 "The minJEVersion must not be null");
249         }
250     }
251 
252     /*
253      * Returns true if the UUID has not as yet been established at this node.
254      * This is the case when a knew node first joins a group, and it has not
255      * as yet replicated the group database via the replication stream.
256      */
hasUnknownUUID()257     public boolean hasUnknownUUID() {
258         return UNKNOWN_UUID.equals(uuid);
259     }
260 
261     /**
262      * Predicate to help determine whether the UUID is the canonical unknown
263      * UUID.
264      */
isUnknownUUID(UUID uuid)265     public static boolean isUnknownUUID(UUID uuid) {
266         return UNKNOWN_UUID.equals(uuid);
267     }
268 
269     /**
270      * Sets the UUID. The UUID can only be set if it's currently unknown.
271      */
setUUID(UUID uuid)272     public void setUUID(UUID uuid) {
273         if (!hasUnknownUUID()) {
274             throw EnvironmentFailureException.unexpectedState
275                 ("Expected placeholder UUID, not " + uuid);
276         }
277         this.uuid = uuid;
278     }
279 
280     /**
281      * Removes a member transiently from the rep group by marking it as removed
282      * and optionally deleting it from the by-name and by-ID maps. This action
283      * is usually a precursor to making the change persistent on disk.
284      *
285      * @param nodeName identifies the node being removed
286      *
287      * @param delete whether to delete the node from the maps
288      *
289      * @return the node that was removed
290      *
291      * @throws EnvironmentFailureException if the node is not part of the group
292      * or is a secondary node
293      */
removeMember(final String nodeName, final boolean delete)294     public RepNodeImpl removeMember(final String nodeName,
295                                     final boolean delete) {
296         final RepNodeImpl node = getMember(nodeName);
297         if (node == null) {
298             throw EnvironmentFailureException.unexpectedState
299                 ("Node:" + nodeName + " is not a member of the group.");
300         }
301         if (node.getType().isSecondary()) {
302             throw EnvironmentFailureException.unexpectedState(
303                 "Cannot remove secondary node: " + nodeName);
304         }
305         if (delete) {
306             synchronized (nodesById) {
307                 nodesById.remove(node.getNodeId());
308                 nodesByName.remove(nodeName);
309             }
310         }
311         node.setRemoved(true);
312         return node;
313     }
314 
315     /**
316      * Checks for whether a new or changed node definition is in conflict with
317      * other members of the group.  In particular, checks that the specified
318      * node does not use the same socket address as another member.
319      * <p>
320      * This check must be done when adding a new member to the group, or
321      * changing the network address of an existing member, and must be done
322      * with the rep group entry in the database locked for write to prevent
323      * race conditions.
324      *
325      * @param node the new node that is being checked for conflicts
326      * @throws NodeConflictException if there is a conflict
327      */
checkForConflicts(RepNodeImpl node)328     public void checkForConflicts(RepNodeImpl node)
329         throws DatabaseException, NodeConflictException {
330 
331         for (RepNodeImpl n : getAllMembers(null)) {
332             if (n.getNameIdPair().equals(node.getNameIdPair())) {
333                 continue;
334             }
335             if (n.getSocketAddress().equals(node.getSocketAddress())) {
336                 throw new NodeConflictException
337                     ("New or moved node:" + node.getName() +
338                      ", is configured with the socket address: " +
339                      node.getSocketAddress() +
340                      ".  It conflicts with the socket already " +
341                      "used by the member: " + n.getName());
342             }
343         }
344     }
345 
346     @Override
hashCode()347     public int hashCode() {
348         final int prime = 31;
349         int result = 1;
350         result = prime * result + changeVersion;
351         result = prime * result
352                 + ((groupName == null) ? 0 : groupName.hashCode());
353         synchronized (nodesById) {
354             result = prime * result + nodesById.hashCode();
355         }
356         /* Don't bother with nodesByName */
357         result = prime * result
358                 + ((uuid == null) ? 0 : uuid.hashCode());
359         result = prime * result + formatVersion;
360         return result;
361     }
362 
363     @Override
equals(Object obj)364     public boolean equals(Object obj) {
365         if (this == obj) {
366             return true;
367         }
368         if (obj == null) {
369             return false;
370         }
371         if (!(obj instanceof RepGroupImpl)) {
372             return false;
373         }
374         RepGroupImpl other = (RepGroupImpl) obj;
375         if (changeVersion != other.changeVersion) {
376             return false;
377         }
378         if (groupName == null) {
379             if (other.groupName != null) {
380                 return false;
381             }
382         } else if (!groupName.equals(other.groupName)) {
383             return false;
384         }
385         /* Don't bother with nodesByName, since nodesById equality covers it */
386         if (uuid == null) {
387             if (other.uuid != null) {
388                 return false;
389             }
390         } else if (!uuid.equals(other.uuid)) {
391             return false;
392         }
393         if (formatVersion != other.formatVersion) {
394             return false;
395         }
396         if (!minJEVersion.equals(other.minJEVersion)) {
397             return false;
398         }
399 
400         /*
401          * Do this last, since it is expensive because of its need to avoid
402          * concurrency conflicts.
403          */
404         final Map<Integer, RepNodeImpl> otherNodesById;
405         synchronized (other.nodesById) {
406             otherNodesById =
407                 new HashMap<Integer, RepNodeImpl>(other.nodesById);
408         }
409         synchronized (nodesById) {
410             if (!nodesById.equals(otherNodesById)) {
411                 return false;
412             }
413         }
414 
415         return true;
416     }
417 
418     /**
419      * Sets the nodes associated with the Rep group. Note that both nodesById
420      * and nodesByIndex are initialized.
421      */
setNodes(final Map<Integer, RepNodeImpl> nodes)422     public void setNodes(final Map<Integer, RepNodeImpl> nodes) {
423 
424         synchronized (nodesById) {
425 
426             /* Remove non-secondary nodes */
427             for (final Iterator<RepNodeImpl> iter =
428                      nodesById.values().iterator();
429                  iter.hasNext(); ) {
430                 final RepNodeImpl node = iter.next();
431                 if (!node.getType().isSecondary()) {
432                     iter.remove();
433                     nodesByName.remove(node.getName());
434                 }
435             }
436 
437             /* Add specified nodes */
438             if (nodes != null) {
439                 for (final RepNodeImpl node : nodes.values()) {
440                     final RepNodeImpl prevById =
441                         nodesById.put(node.getNodeId(), node);
442                     final RepNodeImpl prevByName =
443                         nodesByName.put(node.getName(), node);
444 
445                     /*
446                      * Also remove entries for any previous nodes if the
447                      * mapping between names and IDs was changed.
448                      */
449                     if ((prevById != null) &&
450                         !node.getName().equals(prevById.getName())) {
451                         nodesByName.remove(prevById.getName());
452                     }
453                     if ((prevByName != null) &&
454                         node.getNodeId() != prevByName.getNodeId()) {
455                         nodesById.remove(prevByName.getNodeId());
456                     }
457                 }
458             }
459 
460             assert new HashSet<RepNodeImpl>(nodesById.values()).equals(
461                 new HashSet<RepNodeImpl>(nodesByName.values()))
462                 : "Node maps indexed by ID and name differ: " +
463                 "IDs: " + nodesById + ", Names: " + nodesByName;
464         }
465     }
466 
467     /**
468      * Add a secondary node.  The caller should have already have an assigned
469      * the node an ID and checked that the replication group supports secondary
470      * nodes.
471      *
472      * @param node the secondary node
473      * @throws IllegalStateException if the store does not currently support
474      *         secondary nodes
475      * @throws NodeConflictException if the node conflicts with an existing
476      *         persistent node
477      */
addSecondaryNode(final RepNodeImpl node)478     public void addSecondaryNode(final RepNodeImpl node) {
479         if (!node.getType().isSecondary()) {
480             throw new IllegalArgumentException(
481                 "Attempt to call addSecondaryNode on a non-SECONDARY node: " +
482                 node);
483         }
484         if (node.getNameIdPair().hasNullId()) {
485             throw new IllegalArgumentException(
486                 "Attempt to call addSecondaryNode on node without ID: " +
487                 node);
488         }
489 
490         synchronized (nodesById) {
491             final RepNodeImpl prevById = nodesById.get(node.getNodeId());
492             assert (prevById == null) || prevById.getType().isSecondary()
493                 : "Same node ID for secondary and non-secondary nodes: " +
494                 node + ", " + prevById;
495             final RepNodeImpl prevByName = nodesByName.get(node.getName());
496             if ((prevByName != null) && !prevByName.getType().isSecondary()) {
497                 throw new NodeConflictException(
498                     "New secondary node " + node.getName() +
499                     " conflicts with an existing non-secondary node with the" +
500                     " same name: " + prevByName);
501             }
502             final RepNodeImpl prevById2 =
503                 nodesById.put(node.getNodeId(), node);
504             assert prevById == prevById2;
505             final RepNodeImpl prevByName2 =
506                 nodesByName.put(node.getName(), node);
507             assert prevByName == prevByName2;
508             if ((prevById != null) &&
509                 !node.getName().equals(prevById.getName())) {
510                 nodesByName.remove(prevById.getName());
511             }
512             if ((prevByName != null) &&
513                 (node.getNodeId() != prevByName.getNodeId())) {
514                 nodesById.remove(prevByName.getNodeId());
515             }
516 
517             assert new HashSet<RepNodeImpl>(nodesById.values()).equals(
518                 new HashSet<RepNodeImpl>(nodesByName.values()))
519                 : "Node maps indexed by ID and name differ: " +
520                 "IDs: " + nodesById + ", Names: " + nodesByName;
521         }
522     }
523 
524     /**
525      * Remove a secondary node, which should have an assigned ID
526      *
527      * @param node the secondary node
528      */
removeSecondaryNode(final RepNodeImpl node)529     public void removeSecondaryNode(final RepNodeImpl node) {
530         if (!node.getType().isSecondary()) {
531             throw new IllegalArgumentException(
532                 "Attempt to call removeSecondaryNode on a" +
533                 " non-SECONDARY node: " + node);
534         }
535         if (node.getNameIdPair().hasNullId()) {
536             throw new IllegalArgumentException(
537                 "Attempt to call removeSecondaryNode on a node with no ID: " +
538                 node);
539         }
540         synchronized (nodesById) {
541             nodesById.remove(node.getNodeId());
542             nodesByName.remove(node.getName());
543         }
544     }
545 
546     /**
547      * returns the unique UUID associated with the replicated environment.
548      *
549      * @return the UUID
550      */
getUUID()551     public UUID getUUID() {
552         return uuid;
553     }
554 
555     /**
556      * Returns the version of the format (the schema) in use by this group
557      * instance in the database.
558      *
559      * @return the format version
560      */
getFormatVersion()561     public int getFormatVersion() {
562         return formatVersion;
563     }
564 
565     /**
566      * Returns the highest format version supported by the specified JE
567      * version.
568      *
569      * @param jeVersion the JE version
570      * @return the highest format version supported by that JE version
571      */
getMaxFormatVersion(final JEVersion jeVersion)572     public static int getMaxFormatVersion(final JEVersion jeVersion) {
573         if (jeVersion.compareTo(FORMAT_VERSION_3_JE_VERSION) < 0) {
574             return FORMAT_VERSION_2;
575         }
576         return FORMAT_VERSION_3;
577     }
578 
579     /**
580      * Returns the version of the instance as represented by changes to the
581      * members constituting the group.
582      *
583      * @return the object change version
584      */
getChangeVersion()585     public int getChangeVersion() {
586         return changeVersion;
587     }
588 
589     /**
590      * Increments the object change version. It must be called with the group
591      * entry locked in the group database.
592      *
593      * @return the incremented change version
594      */
incrementChangeVersion()595     public int incrementChangeVersion() {
596         return ++changeVersion;
597     }
598 
599     /**
600      * Returns the current highest node ID currently in use by the group.
601      *
602      * @return the highest node ID in use
603      */
getNodeIdSequence()604     public int getNodeIdSequence() {
605         return nodeIdSequence;
606     }
607 
608     /**
609      * Set the node id sequence. This is only done in unusual circumstances,
610      * e.g. when a replication group is being reset in an existing replicated
611      * environment and we want to ensure that the internal node ids are not
612      * reused in the logs.
613      */
setNodeIdSequence(int nodeIdSequence)614     public void setNodeIdSequence(int nodeIdSequence) {
615         if (nodeIdSequence < 0 || nodeIdSequence > NODE_SEQUENCE_MAX) {
616             throw new IllegalArgumentException(
617                 "Bad nodeIdSequence: " + nodeIdSequence);
618         }
619         this.nodeIdSequence = nodeIdSequence;
620     }
621 
622     /**
623      * Increments the node ID sequence and returns it.
624      *
625      * @return the next node ID for use in a new node
626      */
getNextNodeId()627     public int getNextNodeId() {
628         if (nodeIdSequence >= NODE_SEQUENCE_MAX) {
629             throw new IllegalStateException("Reached maximum node ID");
630         }
631         return ++nodeIdSequence;
632     }
633 
634     /**
635      * Returns the node ID that is associated with the very first node in the
636      * replication group.
637      */
getFirstNodeId()638     public static int getFirstNodeId() {
639         return NODE_SEQUENCE_START+1;
640     }
641 
642     /**
643      * Returns the minimum JE version that a node must be running in order to
644      * join the group.
645      */
getMinJEVersion()646     public JEVersion getMinJEVersion() {
647         return minJEVersion;
648     }
649 
650     /**
651      * Sets the minimum JE version that a node must be running in order to join
652      * the replication group.  The group object should have had its nodes
653      * fetched using the {@link RepGroupDB#fetchGroup} method and should be
654      * stored to the group database after making this change.  Throws a {@link
655      * MinJEVersionUnsupportedException} if the requested version is not
656      * supported.  Updates the group format version as needed to match the JE
657      * version.  Has no effect if the current minimum value is already as high
658      * or higher than the requested one.
659      *
660      * @param newMinJEVersion the new minimum JE version
661      * @throws MinJEVersionUnsupportedException if the requested version is not
662      * supported by the group's electable nodes
663      */
setMinJEVersion(final JEVersion newMinJEVersion)664     public void setMinJEVersion(final JEVersion newMinJEVersion)
665         throws MinJEVersionUnsupportedException {
666 
667         if (newMinJEVersion == null) {
668             throw new IllegalArgumentException(
669                 "The newMinJEVersion argument must not be null");
670         }
671         if (newMinJEVersion.compareTo(minJEVersion) <= 0) {
672             return;
673         }
674         final int newFormatVersion = getMaxFormatVersion(newMinJEVersion);
675 
676         /* Minimum JE version is not stored before format version 3 */
677         if (newFormatVersion < FORMAT_VERSION_3) {
678             return;
679         }
680 
681         for (final RepNodeImpl node : getElectableMembers()) {
682             final JEVersion nodeJEVersion = node.getJEVersion();
683             if ((nodeJEVersion != null) &&
684                 nodeJEVersion.compareTo(newMinJEVersion) < 0) {
685                 throw new MinJEVersionUnsupportedException(
686                     newMinJEVersion, node.getName(), nodeJEVersion);
687             }
688         }
689         minJEVersion = newMinJEVersion;
690         formatVersion = newFormatVersion;
691     }
692 
693     /**
694      * Used to ensure that the ReplicationGroup value is consistent after it
695      * has been fetched via a readUncommitted access to the rep group database.
696      * It does so by ensuring that the summarized values match the nodes that
697      * were actually read.
698      */
makeConsistent()699     public void makeConsistent() {
700         synchronized (nodesById) {
701             if (nodesById.isEmpty()) {
702                 return;
703             }
704             int computedNodeId = NODE_SEQUENCE_START-1;
705             int computedChangeVersion = -1;
706             for (RepNodeImpl mi : nodesById.values()) {
707                 /* Get the highest node ID */
708                 if (computedNodeId < mi.getNodeId()) {
709                     computedNodeId = mi.getNodeId();
710                 }
711                 /* Get the highest change version. */
712                 if (computedChangeVersion < mi.getChangeVersion()) {
713                     computedChangeVersion = mi.getChangeVersion();
714                 }
715             }
716             setNodeIdSequence(computedNodeId);
717             changeVersion = computedChangeVersion;
718         }
719     }
720 
721     /*
722      * Serialization
723      */
724 
725     /**
726      * Serializes an object by converting its TupleBinding byte based
727      * representation into the hex characters denoting the bytes.
728      *
729      * @param <T> the type of the object being serialized
730      * @param binding the tuble binding used to convert it into its byte form
731      * @param object the object being serialized
732      * @return the hex string containing the serialized hex form of the object
733      */
objectToHex(TupleBinding<T> binding, T object)734     static <T> String objectToHex(TupleBinding<T> binding, T object) {
735         StringBuilder buffer = new StringBuilder();
736         TupleOutput tuple = new TupleOutput(new byte[100]);
737         binding.objectToEntry(object, tuple);
738         byte[] bytes = tuple.getBufferBytes();
739         int size = tuple.getBufferLength();
740 
741         for (int i=0; i < size; i++) {
742             int lowNibble = (bytes[i] & 0xf);
743             int highNibble = ((bytes[i]>>4) & 0xf);
744             buffer.append(Character.forDigit(lowNibble,16));
745             buffer.append(Character.forDigit(highNibble,16));
746         }
747         return buffer.toString();
748     }
749 
750     /**
751      * Returns a serialized character based form of the group suitable for use
752      * in subclasses of SimpleProtocol. The serialized form is a multi-token
753      * string. The first token represents the RepGroup object itself with each
754      * subsequent node representing a node in the group. Tokens are separated
755      * by '|', the protocol separator character. The number of tokens is thus
756      * equal to the number of nodes in the group + 1. Each token is itself a
757      * hex character based representation of the binding used to serialize a
758      * RepGroup and store it into the database.
759      *
760      * @param groupFormatVersion the group format version
761      * @return the string encoded as above
762      */
serializeHex(final int groupFormatVersion)763     public String serializeHex(final int groupFormatVersion) {
764         final RepGroupDB.GroupBinding groupBinding =
765             new RepGroupDB.GroupBinding(groupFormatVersion);
766         StringBuilder buffer = new StringBuilder();
767         buffer.append(objectToHex(groupBinding, this));
768         synchronized (nodesById) {
769             for (RepNodeImpl mi : nodesById.values()) {
770 
771                 /*
772                  * Only include nodes that can be serialized with the specified
773                  * format version
774                  */
775                 if (NodeBinding.supportsObjectToEntry(
776                         mi, groupFormatVersion)) {
777                     buffer.append(TextProtocol.SEPARATOR);
778                     buffer.append(serializeHex(mi, groupFormatVersion));
779                 }
780             }
781         }
782         return buffer.toString();
783     }
784 
785     /**
786      * Returns the serialized form of the node as a sequence of hex characters
787      * suitable for use by the text based protocols.
788      *
789      * @param node the node to be serialized.
790      * @param formatVersion the group format version
791      * @return the string containing the serialized form of the node
792      */
serializeHex(final RepNodeImpl node, final int formatVersion)793     public static String serializeHex(final RepNodeImpl node,
794                                       final int formatVersion) {
795         final NodeBinding nodeBinding = new NodeBinding(formatVersion);
796         return objectToHex(nodeBinding, node);
797     }
798 
799     /**
800      * Serialize the node into its byte representation.
801      *
802      * @param node the node to be serialized
803      * @param formatVersion the group format version
804      * @return the serialized byte array
805      */
serializeBytes(final RepNodeImpl node, final int formatVersion)806     public static byte[] serializeBytes(final RepNodeImpl node,
807                                         final int formatVersion) {
808 
809         final NodeBinding binding = new NodeBinding(formatVersion);
810         final TupleOutput tuple =
811             new TupleOutput(new byte[NodeBinding.APPROX_MAX_SIZE]);
812         binding.objectToEntry(node, tuple);
813         return tuple.getBufferBytes();
814     }
815 
816     /**
817      * Deserializes the object serialized by {@link #serializeHex}
818      *
819      * @param hex the string containing the serialized form of the node
820      * @param formatVersion the group format version
821      *
822      * @return the de-serialized object
823      */
hexDeserializeNode(final String hex, final int formatVersion)824     public static RepNodeImpl hexDeserializeNode(final String hex,
825                                                  final int formatVersion) {
826         final NodeBinding nodeBinding = new NodeBinding(formatVersion);
827         return hexToObject(nodeBinding, hex);
828     }
829 
830     /**
831      * Deserialize the mode from its byte representation.
832      *
833      * @param bytes the byte representation of the node.
834      * @param formatVersion the group format version
835      *
836      * @return the deserialized object
837      */
deserializeNode(final byte[] bytes, final int formatVersion)838     public static RepNodeImpl deserializeNode(final byte[] bytes,
839                                               final int formatVersion) {
840         final NodeBinding binding = new NodeBinding(formatVersion);
841         TupleInput tuple = new TupleInput(bytes);
842         return binding.entryToObject(tuple);
843     }
844 
845     /**
846      * Carries out the two step de-serialization from hex string into a byte
847      * buffer and subsequently into its object representation.
848      *
849      * @return the object representation
850      */
hexToObject(TupleBinding<T> binding, String hex)851     private static <T> T hexToObject(TupleBinding<T> binding, String hex) {
852         byte buffer[] = new byte[(hex.length()/2)];
853         for (int i=0; i < hex.length(); i+=2) {
854             int value = Character.digit(hex.charAt(i),16);
855             value |= Character.digit(hex.charAt(i+1),16) << 4;
856             buffer[i >> 1] = (byte)value;
857         }
858         TupleInput tuple = new TupleInput(buffer);
859         return binding.entryToObject(tuple);
860     }
861 
862     /**
863      * De-serializes an array of tokens into a Rep group object and its nodes.
864      * the token at <code>start</code>represents the group object and each
865      * subsequent token represents a node in the group.
866      *
867      * @param tokens the array representing the group and its nodes
868      * @param start the position in the array at which to start the
869      * de-serialization.
870      *
871      * @return the de-serialized RepGroup
872      */
deserializeHex(String[] tokens, int start)873     static public RepGroupImpl deserializeHex(String[] tokens, int start) {
874         final RepGroupDB.GroupBinding groupBinding =
875             new RepGroupDB.GroupBinding();
876         RepGroupImpl group = hexToObject(groupBinding, tokens[start++]);
877         Map<Integer,RepNodeImpl> nodeMap =
878             new HashMap<Integer,RepNodeImpl>();
879         while (start < tokens.length) {
880             RepNodeImpl n =
881                 hexDeserializeNode(tokens[start++], group.getFormatVersion());
882             RepNodeImpl old = nodeMap.put(n.getNameIdPair().getId(), n);
883             assert(old == null);
884         }
885         group.setNodes(nodeMap);
886         return group;
887     }
888 
889     /*
890      * Accessing nodes and groups of nodes
891      */
892 
893     /**
894      * Returns the node IDs for all nodes that are currently members of the
895      * group and that act as proposers, acceptors, or distinguished learners.
896      * Returns IDs for all ELECTABLE and MONITOR nodes that are not removed,
897      * even if they are not acknowledged, but not for SECONDARY nodes.
898      */
getAllElectionMemberIds()899     public Set<Integer> getAllElectionMemberIds() {
900         Set<Integer> ret = new HashSet<Integer>();
901         synchronized (nodesById) {
902             for (RepNodeImpl mi : nodesById.values()) {
903                 if (!mi.isRemoved() && !mi.getType().isSecondary()) {
904                     ret.add(mi.getNodeId());
905                 }
906             }
907         }
908         return ret;
909     }
910 
911     /**
912      * Returns all nodes that are currently members of the group.  Returns all
913      * ELECTABLE and MONITOR nodes that are not removed, even if they are not
914      * acknowledged, and SECONDARY nodes.  If the predicate is not null, only
915      * includes members that satisfy the predicate.
916      */
getAllMembers(final Predicate p)917     public Set<RepNodeImpl> getAllMembers(final Predicate p) {
918         final Set<RepNodeImpl> result = new HashSet<RepNodeImpl>();
919         includeAllMembers(p, result);
920         return result;
921     }
922 
923     /**
924      * Adds all nodes that are currently members of the group to the specified
925      * set.  Adds all ELECTABLE and MONITOR nodes that are not removed, even if
926      * they are not acknowledged, and SECONDARY nodes.  If the predicate is not
927      * null, only adds members that satisfy the predicate.
928      */
includeAllMembers(final Predicate p, final Set<? super RepNodeImpl> set)929     public void includeAllMembers(final Predicate p,
930                                   final Set<? super RepNodeImpl> set) {
931         synchronized (nodesById) {
932             for (RepNodeImpl mi : nodesById.values()) {
933                 if (!mi.isRemoved() && ((p == null) || p.include(mi))) {
934                     set.add(mi);
935                 }
936             }
937         }
938     }
939 
940     /**
941      * Counts the number of nodes that are currently members of the group.
942      * Counts all ELECTABLE and MONITOR nodes that are not removed, even if
943      * they are not acknowledged, and SECONDARY nodes.  If the predicate is not
944      * null, only counts members that satisfy the predicate.
945      */
countAllMembers(final Predicate p)946     public int countAllMembers(final Predicate p) {
947         int count = 0;
948         synchronized (nodesById) {
949             for (final RepNodeImpl mi : nodesById.values()) {
950                 if (!mi.isRemoved() && ((p == null) || p.include(mi))) {
951                     count++;
952                 }
953             }
954         }
955         return count;
956     }
957 
958     /**
959      * Adds nodes that are currently members of the group to the specified set.
960      * Adds ELECTABLE and MONITOR node that are not removed and are
961      * acknowledged, and SECONDARY nodes.  If the predicate is not null, only
962      * adds members that satisfy the predicate.
963      */
includeMembers(final Predicate p, final Set<? super RepNodeImpl> set)964     public void includeMembers(final Predicate p,
965                                final Set<? super RepNodeImpl> set) {
966         synchronized (nodesById) {
967             for (RepNodeImpl n : nodesById.values()) {
968                 if (!n.isRemoved() &&
969                     n.isQuorumAck() &&
970                     ((p == null) || p.include(n))) {
971                     set.add(n);
972                 }
973             }
974         }
975     }
976 
977     /**
978      * Gets the node that is currently a member of the group that has the given
979      * socket address.  Returns ELECTABLE and MONITOR nodes that are not
980      * removed, even if it is not acknowledged, and SECONDARY nodes.
981      *
982      * @return the desired node, or null if there is no such node, including
983      *         if it was removed
984      */
getMember(InetSocketAddress socket)985     public RepNodeImpl getMember(InetSocketAddress socket) {
986         synchronized (nodesById) {
987             for (RepNodeImpl n : nodesById.values()) {
988                 if (socket.equals(n.getSocketAddress()) && !n.isRemoved()) {
989                     return n;
990                 }
991             }
992         }
993         return null;
994     }
995 
996     /**
997      * Returns nodes that are removed from the group.  Returns ELECTABLE and
998      * MONITOR nodes that are removed and are acknowledged, but not SECONDARY
999      * nodes, which are not remembered when they are removed.
1000      */
getRemovedNodes()1001     public Set<RepNodeImpl> getRemovedNodes() {
1002         Set<RepNodeImpl> ret = new HashSet<RepNodeImpl>();
1003         synchronized (nodesById) {
1004             for (RepNodeImpl mi : nodesById.values()) {
1005                 if (mi.isRemoved() && mi.isQuorumAck()) {
1006                     ret.add(mi);
1007                 }
1008             }
1009         }
1010         return ret;
1011     }
1012 
1013     /** A predicate for specifying which replication nodes to include. */
1014     abstract static class Predicate {
include(RepNodeImpl n)1015         abstract boolean include(RepNodeImpl n);
1016     }
1017 
1018     /**
1019      * Returns all electable nodes that are currently members of the group.
1020      * Returns all ELECTABLE nodes that are not removed, even if they are not
1021      * acknowledged, but not MONITOR or SECONDARY nodes.
1022      */
getAllElectableMembers()1023     public Set<RepNodeImpl> getAllElectableMembers() {
1024         return getAllMembers(ELECTABLE_PREDICATE);
1025     }
1026 
1027     /**
1028      * Returns electable nodes that are currently members of the group.
1029      * Returns ELECTABLE nodes that are not removed and are acknowledged, but
1030      * not MONITOR or SECONDARY nodes.
1031      */
getElectableMembers()1032     public Set<RepNodeImpl> getElectableMembers() {
1033         final Set<RepNodeImpl> result = new HashSet<RepNodeImpl>();
1034         includeElectableMembers(result);
1035         return result;
1036     }
1037 
1038     /**
1039      * Adds the electable nodes that are currently members of the group to the
1040      * specified set.  Adds ELECTABLE nodes that are not removed and are
1041      * acknowledged, but not MONITOR or SECONDARY nodes.
1042      */
includeElectableMembers(final Set<? super RepNodeImpl> set)1043     public void includeElectableMembers(final Set<? super RepNodeImpl> set) {
1044         includeAllMembers(
1045             new Predicate() {
1046                 @Override
1047                 boolean include(RepNodeImpl n) {
1048                     return n.getType().isElectable() && n.isQuorumAck();
1049                 }
1050             },
1051             set);
1052     }
1053 
1054     /**
1055      * Returns the nodes that are currently members of the group that store
1056      * replication data.  Returns ELECTABLE nodes that are not removed and are
1057      * acknowledged, and SECONDARY nodes, but not MONITOR nodes.
1058      */
getDataMembers()1059     public Set<RepNodeImpl> getDataMembers() {
1060         final Set<RepNodeImpl> result = new HashSet<RepNodeImpl>();
1061         includeDataMembers(result);
1062         return result;
1063     }
1064 
1065     /**
1066      * Adds the nodes that are currently members of the group that store
1067      * replication data to the specified set.  Adds ELECTABLE nodes that are
1068      * not removed and are acknowledged, and SECONDARY nodes, but not MONITOR
1069      * nodes.
1070      */
includeDataMembers(final Set<? super RepNodeImpl> set)1071     public void includeDataMembers(final Set<? super RepNodeImpl> set) {
1072         includeAllMembers(
1073             new Predicate() {
1074                 @Override
1075                 boolean include(final RepNodeImpl n) {
1076                     return n.getType().isDataNode() && n.isQuorumAck();
1077                 }
1078             },
1079             set);
1080     }
1081 
1082     /**
1083      * Returns the monitor nodes that are currently members of the group.
1084      * Returns MONITOR nodes that are not removed and are acknowledged, but not
1085      * ELECTABLE or SECONDARY nodes.
1086      *
1087      * @return the set of monitor nodes
1088      */
getMonitorMembers()1089     public Set<RepNodeImpl> getMonitorMembers() {
1090         final Set<RepNodeImpl> result = new HashSet<RepNodeImpl>();
1091         includeMonitorMembers(result);
1092         return result;
1093     }
1094 
1095     /**
1096      * Adds the monitor nodes that are currently members of the group to the
1097      * specified set.  Adds MONITOR nodes that are not removed and are
1098      * acknowledged, but not ELECTABLE or SECONDARY nodes.
1099      */
includeMonitorMembers(final Set<? super RepNodeImpl> set)1100     public void includeMonitorMembers(final Set<? super RepNodeImpl> set) {
1101         includeMembers(MONITOR_PREDICATE, set);
1102     }
1103 
1104     /**
1105      * Returns all the nodes that are currently members of the group that act
1106      * as distinguished learners to receive election results.  Returns all
1107      * ELECTABLE and MONITOR that are not removed, even if they are not
1108      * acknowledged, but not SECONDARY nodes.
1109      */
getAllLearnerMembers()1110     public Set<RepNodeImpl> getAllLearnerMembers() {
1111         final Set<RepNodeImpl> result = new HashSet<RepNodeImpl>();
1112         includeAllMembers(
1113             new Predicate() {
1114                 @Override
1115                 boolean include(final RepNodeImpl n) {
1116                     return (n.getType().isElectable() ||
1117                             n.getType().isMonitor());
1118                 }
1119             },
1120             result);
1121         return result;
1122     }
1123 
1124     /**
1125      * Returns the secondary nodes that are currently members of the group.
1126      * Returns SECONDARY nodes, but not ELECTABLE or MONITOR nodes.
1127      */
getSecondaryMembers()1128     public Set<RepNodeImpl> getSecondaryMembers() {
1129         final Set<RepNodeImpl> result = new HashSet<RepNodeImpl>();
1130         includeSecondaryMembers(result);
1131         return result;
1132     }
1133 
1134     /**
1135      * Adds the secondary nodes that are currently members of the group to the
1136      * specified set.  Adds SECONDARY nodes, but not ELECTABLE or MONITOR
1137      * nodes.
1138      */
includeSecondaryMembers(final Set<? super RepNodeImpl> set)1139     public void includeSecondaryMembers(final Set<? super RepNodeImpl> set) {
1140         includeAllMembers(SECONDARY_PREDICATE, set);
1141     }
1142 
1143     /**
1144      * Returns the socket addresses for all nodes that are currently members of
1145      * the group.  Returns addresses for all ELECTABLE and MONITOR nodes that
1146      * are not removed, even if they are not acknowledged, and for all
1147      * SECONDARY nodes.  If the predicate is not null, only returns addresses
1148      * for members that satisfy the predicate.
1149      */
getAllMemberSockets(Predicate p)1150     private Set<InetSocketAddress> getAllMemberSockets(Predicate p) {
1151         Set<InetSocketAddress> sockets = new HashSet<InetSocketAddress>();
1152         synchronized (nodesById) {
1153             for (final RepNodeImpl mi : nodesById.values()) {
1154                 if ((((mi.getType().isElectable() ||
1155                        mi.getType().isMonitor()) &&
1156                       !mi.isRemoved()) ||
1157                      mi.getType().isSecondary()) &&
1158                     ((p == null) || p.include(mi))) {
1159                     sockets.add(mi.getSocketAddress());
1160                 }
1161             }
1162         }
1163         return sockets;
1164     }
1165 
1166     /**
1167      * Return the socket addresses for all nodes that are currently members of
1168      * the group and act as distinguished learners to receive election results.
1169      * Returns addresses for all ELECTABLE and MONITOR nodes that are not
1170      * removed, even if they are not acknowledged, but not for SECONDARY nodes.
1171      *
1172      * @return set of learner socket addresses
1173      */
getAllLearnerSockets()1174     public Set<InetSocketAddress> getAllLearnerSockets() {
1175 
1176         /*
1177          * TODO: Consider including SECONDARY nodes in this list.  That change
1178          * would increase the chance that SECONDARY nodes have up-to-date
1179          * information about the master, but would need to be paired with a
1180          * change to only wait for delivery of notifications to ELECTABLE
1181          * nodes, to avoid adding sensitivity to potentially longer network
1182          * delays in communicating with secondary nodes.
1183          */
1184         return getAllMemberSockets(new Predicate() {
1185             @Override
1186             boolean include(RepNodeImpl n) {
1187                 return !n.getType().isSecondary();
1188             }
1189         });
1190     }
1191 
1192     /**
1193      * Return the socket addresses for all nodes that are currently members of
1194      * the group and act as helpers to supply election results.  Returns
1195      * addresses for all ELECTABLE and MONITOR nodes that are not removed, even
1196      * if they are not acknowledged, and SECONDARY nodes.
1197      *
1198      * @return set of helper socket addresses
1199      */
1200     public Set<InetSocketAddress> getAllHelperSockets() {
1201         return getAllMemberSockets(null);
1202     }
1203 
1204     /**
1205      * Returns the socket addresses for all monitor nodes that are currently
1206      * members of the group.  Returns addresses for all MONITOR nodes that are
1207      * not removed, even if they are not acknowledged, but not for ELECTABLE or
1208      * SECONDARY nodes.
1209      *
1210      * @return the set of Monitor socket addresses
1211      */
1212     public Set<InetSocketAddress> getAllMonitorSockets() {
1213         return getAllMemberSockets(MONITOR_PREDICATE);
1214     }
1215 
1216     /**
1217      * Returns the socket addresses for all nodes that are currently members of
1218      * the group and act as acceptors for elections.  Returns addresses for all
1219      * ELECTABLE nodes that are not removed, even if they are not acknowledged,
1220      * but not for MONITOR or SECONDARY nodes, which do not act as acceptors.
1221      *
1222      * @return the set of acceptor socket addresses
1223      */
1224     public Set<InetSocketAddress> getAllAcceptorSockets() {
1225         return getAllMemberSockets(ELECTABLE_PREDICATE);
1226     }
1227 
1228     /**
1229      * Returns the node with the specified ID that is currently a member of the
1230      * group, throwing an exception if the node is found but is no longer a
1231      * member.  Returns ELECTABLE and MONITOR nodes that are not removed, even
1232      * if they are not acknowledged, and SECONDARY nodes.
1233      *
1234      * @param nodeId the node ID
1235      * @return the member or null
1236      * @throws EnvironmentFailureException if the node is no longer a member
1237      */
1238     public RepNodeImpl getMember(int nodeId) {
1239         RepNodeImpl node = getNode(nodeId);
1240         if (node == null) {
1241             return null;
1242         }
1243         if (node.isRemoved()) {
1244             throw EnvironmentFailureException.unexpectedState
1245                 ("No longer a member:" + nodeId);
1246         }
1247         return node;
1248     }
1249 
1250     /**
1251      * Returns the node with the specified name that is currently a member of
1252      * the group, throwing an exception if the node is found but is no longer a
1253      * member.  Returns ELECTABLE and MONITOR nodes that are not removed, even
1254      * if they are not acknowledged, and SECONDARY nodes.
1255      *
1256      * @param name the node name
1257      * @return the member or null
1258      * @throws MemberNotFoundException if the node is no longer a member
1259      */
1260     public RepNodeImpl getMember(String name)
1261         throws MemberNotFoundException {
1262 
1263         RepNodeImpl node = getNode(name);
1264         if (node == null) {
1265             return null;
1266         }
1267         if (node.isRemoved()) {
1268             throw new MemberNotFoundException
1269                 ("Node no longer a member:" + name);
1270         }
1271         return node;
1272     }
1273 
1274     /**
1275      * Returns the node with the specified ID, regardless of its membership
1276      * state.  Returns all ELECTABLE and MONITOR nodes, even if they are
1277      * removed or are not acknowledged, and SECONDARY nodes.
1278      *
1279      *  @return the node or null
1280      */
1281     public RepNodeImpl getNode(int nodeId) {
1282         synchronized (nodesById) {
1283             return nodesById.get(nodeId);
1284         }
1285     }
1286 
1287     /**
1288      * Returns the node with the specified name, regardless of its membership
1289      * state.  Returns all ELECTABLE and MONITOR nodes, even if they are
1290      * removed or are not acknowledged, and SECONDARY nodes.
1291      *
1292      *  @return the node or null
1293      */
1294     public RepNodeImpl getNode(String name) {
1295         synchronized (nodesById) {
1296             return nodesByName.get(name);
1297         }
1298     }
1299 
1300     /**
1301      * Returns the number of all electable nodes that are currently members of
1302      * the group.  Includes all ELECTABLE nodes that are not removed, even if
1303      * they are not acknowledged, but not MONITOR or SECONDARY nodes.  Note
1304      * that even unACKed nodes are considered part of the group for group
1305      * size/durability considerations.
1306      *
1307      * @return the size of the group for durability considerations
1308      */
1309     public int getElectableGroupSize() {
1310         return countAllMembers(ELECTABLE_PREDICATE);
1311     }
1312 
1313     /* Miscellaneous */
1314 
1315     /**
1316      * Returns the name of the group.
1317      *
1318      * @return the name of the group.
1319      */
1320     public String getName() {
1321         return groupName;
1322     }
1323 
1324     /**
1325      * Encapsulates the last known syncup state associated with a node.
1326      */
1327     public static class BarrierState implements Serializable {
1328 
1329         private static final long serialVersionUID = 1L;
1330 
1331         /*
1332          * The latest sync position of this node in the replication stream.
1333          * This position is approximate and is updated on some regular basis.
1334          * It is conservative in that the node is likely to have a newer sync
1335          * point. So it represents a lower bound for its sync point.
1336          */
1337         private final VLSN lastLocalCBVLSN;
1338 
1339         /*
1340          * The time that the sync point was last recorded. Note that clocks
1341          * should be reasonably synchronized.
1342          */
1343         private final long barrierTime;
1344 
1345         public BarrierState(VLSN lastLocalCBVLSN, long barrierTime) {
1346             super();
1347             this.lastLocalCBVLSN = lastLocalCBVLSN;
1348             this.barrierTime = barrierTime;
1349         }
1350 
1351         @Override
1352         public int hashCode() {
1353             final int prime = 31;
1354             int result = 1;
1355             result = prime * result +
1356                     (lastLocalCBVLSN == null ? 0 : lastLocalCBVLSN.hashCode());
1357             result = prime * result +
1358                      (int) (barrierTime ^ (barrierTime >>> 32));
1359             return result;
1360         }
1361 
1362         @Override
1363         public boolean equals(Object obj) {
1364             if (this == obj) {
1365                 return true;
1366             }
1367             if (obj == null) {
1368                 return false;
1369             }
1370             if (getClass() != obj.getClass()) {
1371                 return false;
1372             }
1373             BarrierState other = (BarrierState) obj;
1374             if (lastLocalCBVLSN == null) {
1375                 if (other.lastLocalCBVLSN != null) {
1376                     return false;
1377                 }
1378             } else if (!lastLocalCBVLSN.equals(other.lastLocalCBVLSN)) {
1379                 return false;
1380             }
1381             if (barrierTime != other.barrierTime) {
1382                 return false;
1383             }
1384             return true;
1385         }
1386 
1387         public VLSN getLastCBVLSN() {
1388             return lastLocalCBVLSN;
1389         }
1390 
1391         public long getBarrierTime() {
1392             return barrierTime;
1393         }
1394 
1395         @Override
1396         public String toString() {
1397             return String.format("LocalCBVLSN:%,d at:%tc",
1398                                  lastLocalCBVLSN.getSequence(), barrierTime);
1399         }
1400     }
1401 
1402     /*
1403      * An internal exception indicating that two nodes have conflicting
1404      * configurations. For example, they both use the same hostname and port.
1405      */
1406     @SuppressWarnings("serial")
1407     public static class NodeConflictException extends DatabaseException {
1408         public NodeConflictException(String message) {
1409             super(message);
1410         }
1411     }
1412 
1413     /**
1414      * Return information to the user, format nicely for ease of reading.
1415      */
1416     @Override
1417     public String toString() {
1418         StringBuilder sb = new StringBuilder();
1419         sb.append("Group info [").append(groupName).append("] ");
1420         sb.append(getUUID()).
1421             append("\n Format version: ").append(getFormatVersion()).
1422             append("\n Change version: ").append(getChangeVersion()).
1423             append("\n Max persist rep node ID: ").append(getNodeIdSequence()).
1424             append("\n Min JE version: ").append(minJEVersion).
1425             append("\n");
1426 
1427         synchronized (nodesById) {
1428             for (final RepNodeImpl node : nodesById.values()) {
1429                 sb.append(" ").append(node);
1430             }
1431         }
1432         return sb.toString();
1433     }
1434 }
1435