blob: 43ac479dfac8f80768eb8ef6a0e310bb02303c7a [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.pulsar.common.naming;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Objects;
import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
public class NamespaceBundle implements ServiceUnitId, Comparable<NamespaceBundle> {
private final NamespaceName nsname;
private final Range<Long> keyRange;
private final NamespaceBundleFactory factory;
// Issue#596: remove this once we remove broker persistent/non-persistent mode configuration
// it is used by load-manager while considering bundle ownership
private boolean hasNonPersistentTopic = false;
public NamespaceBundle(NamespaceName nsname, Range<Long> keyRange, NamespaceBundleFactory factory) {
this.nsname = checkNotNull(nsname);
this.keyRange = checkNotNull(keyRange);
checkArgument(this.keyRange.lowerBoundType().equals(BoundType.CLOSED),
"Invalid hash range. Lower Endpoint has to be inclusive");
checkArgument(
(this.keyRange.upperEndpoint().equals(NamespaceBundles.FULL_UPPER_BOUND)
&& this.keyRange.upperBoundType().equals(BoundType.CLOSED))
|| (!this.keyRange.upperEndpoint().equals(NamespaceBundles.FULL_UPPER_BOUND)
&& this.keyRange.upperBoundType().equals(BoundType.OPEN)),
"Invalid hash range. Upper Endpoint should be exclusive unless it is 0xffffffff");
checkArgument(!this.keyRange.isEmpty(), "Cannot create bundle object for an empty key range");
this.factory = checkNotNull(factory);
}
@Override
public NamespaceName getNamespaceObject() {
return this.nsname;
}
@Override
public String toString() {
return getKey(this.nsname, this.keyRange);
}
@Override
public int compareTo(NamespaceBundle other) {
if (this.nsname.toString().compareTo(other.nsname.toString()) != 0) {
return this.nsname.toString().compareTo(other.nsname.toString());
}
if (equals(other)) {
// completely the same range, return true
return 0;
}
try {
/**
* <code>Range.intersection()</code> will throw <code>IllegalArgumentException</code> when two ranges are
* not connected at all, which is a OK case for our comparison. <code>checkState</code> here is to ensure
* that the two ranges we are comparing don't have overlaps.
*/
checkState(this.keyRange.intersection(other.keyRange).isEmpty(),
"Can't compare two key ranges with non-empty intersection set");
} catch (IllegalArgumentException iae) {
// OK if two ranges are not connected at all
} catch (IllegalStateException ise) {
// It is not OK if the intersection is not empty
throw new IllegalArgumentException(ise.getMessage(), ise);
}
// Now we are sure that two bundles don't have overlap
return this.keyRange.lowerEndpoint().compareTo(other.keyRange.lowerEndpoint());
}
@Override
public int hashCode() {
return Objects.hashCode(nsname,
keyRange.lowerEndpoint(), keyRange.lowerBoundType(),
keyRange.upperEndpoint(), keyRange.upperBoundType());
}
@Override
public boolean equals(Object other) {
if (other instanceof NamespaceBundle) {
NamespaceBundle obj = (NamespaceBundle) other;
return Objects.equal(this.nsname, obj.nsname)
&& (Objects.equal(this.keyRange.lowerEndpoint(), obj.keyRange.lowerEndpoint())
&& Objects.equal(this.keyRange.lowerBoundType(), obj.keyRange.lowerBoundType())
&& Objects.equal(this.keyRange.upperEndpoint(), obj.keyRange.upperEndpoint())
&& Objects.equal(this.keyRange.upperBoundType(), obj.keyRange.upperBoundType()));
}
return false;
}
@Override
public boolean includes(TopicName topicName) {
if (!this.nsname.equals(topicName.getNamespaceObject())) {
return false;
}
return this.keyRange.contains(factory.getLongHashCode(topicName.toString()));
}
public String getBundleRange() {
return String.format("0x%08x_0x%08x", keyRange.lowerEndpoint(), keyRange.upperEndpoint());
}
private static String getKey(NamespaceName nsname, Range<Long> keyRange) {
return String.format("%s/0x%08x_0x%08x", nsname, keyRange.lowerEndpoint(), keyRange.upperEndpoint());
}
Range<Long> getKeyRange() {
return this.keyRange;
}
Long getLowerEndpoint() {
return this.keyRange.lowerEndpoint();
}
Long getUpperEndpoint() {
return this.keyRange.upperEndpoint();
}
public boolean hasNonPersistentTopic() {
return hasNonPersistentTopic;
}
public void setHasNonPersistentTopic(boolean hasNonPersistentTopic) {
this.hasNonPersistentTopic = hasNonPersistentTopic;
}
public static String getBundleRange(String namespaceBundle) {
return namespaceBundle.substring(namespaceBundle.lastIndexOf('/') + 1);
}
public NamespaceBundleFactory getNamespaceBundleFactory() {
return factory;
}
}