com.hivemq.util.Topics Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hivemq-community-edition-embedded Show documentation
Show all versions of hivemq-community-edition-embedded Show documentation
HiveMQ CE is a Java-based open source MQTT broker that fully supports MQTT 3.x and MQTT 5
The newest version!
/*
* Copyright 2019-present HiveMQ GmbH
*
* Licensed 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 com.hivemq.util;
import com.hivemq.extension.sdk.api.annotations.NotNull;
import com.hivemq.persistence.clientsession.SharedSubscriptionService.SharedSubscription;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A Utility class for dealing with topics
*
* @author Dominik Obermaier
*/
public class Topics {
private static final char[] SHARED_SUBSCRIPTION_CHAR_ARRAY = "$share".toCharArray();
private static final int SHARED_SUBSCRIPTION_LENGTH = SHARED_SUBSCRIPTION_CHAR_ARRAY.length;
private static final char SHARED_SUBSCRIPTION_DELIMITER = '/';
private static final int GROUP_INDEX = 2;
private static final int TOPIC_INDEX = 3;
/**
* The multi-level wildcard character.
*/
private static final char MULTI_LEVEL_WILDCARD = '#';
/**
* The single-level wildcard character.
*/
private static final char SINGLE_LEVEL_WILDCARD = '+';
private static final Pattern SHARED_SUBSCRIPTION_PATTERN = Pattern.compile("\\$share(/(.*?)/(.*))");
/**
* Check if a topic is a shared subscription topic.
*
* @param topic the topic to check
* @return true if it is a shared subscription, else false.
*/
public static boolean isSharedSubscriptionTopic(@NotNull final String topic) {
//optimizing
if (!topic.startsWith("$share/")) {
return false;
}
final Matcher matcher = SHARED_SUBSCRIPTION_PATTERN.matcher(topic);
return matcher.matches();
}
/**
* Checks if the topic is valid to publish to.
*
* This checks for invalid
*
*
* - #
* - +
* - illegal UTF-8 chars
*
*
* @param topic the topic to check
* @return true
if the topic is valid, false
otherwise
*/
public static boolean isValidTopicToPublish(@NotNull final String topic) {
if (topic.isEmpty()) {
return false;
}
if (topic.contains("\u0000")) {
return false;
}
//noinspection IndexOfReplaceableByContains
return !(topic.indexOf("#") > -1 || topic.indexOf("+") > -1);
}
/**
* Checks if the topic is valid to subscribe to.
*
* This check for invalid
*
*
* - # combinations
* - + combination
* - illegal UTF-8 chars
*
*
* @param topic the topic to check
* @return true
if the topic is valid, false
otherwise
*/
public static boolean isValidToSubscribe(@NotNull final String topic) {
if (topic.isEmpty()) {
return false;
}
if (topic.contains("\u0000")) {
return false;
}
//We're using charAt because otherwise the String backing char[]
//needs to be copied. JMH Benchmarks showed that this is more performant
char lastChar = topic.charAt(0);
char currentChar;
int sharedSubscriptionDelimiterCharCount = 0;
final int length = topic.length();
boolean isSharedSubscription = false;
int sharedCounter = lastChar == SHARED_SUBSCRIPTION_CHAR_ARRAY[0] ? 1 : -1;
for (int i = 1; i < length; i++) {
currentChar = topic.charAt(i);
// current char still matching $share ?
if (i < SHARED_SUBSCRIPTION_LENGTH && currentChar == SHARED_SUBSCRIPTION_CHAR_ARRAY[i]) {
sharedCounter++;
}
// finally, is it a shared subscription?
if (i == SHARED_SUBSCRIPTION_LENGTH &&
sharedCounter == SHARED_SUBSCRIPTION_LENGTH &&
currentChar == SHARED_SUBSCRIPTION_DELIMITER) {
isSharedSubscription = true;
}
//Check the shared name
if (isSharedSubscription && sharedSubscriptionDelimiterCharCount == 1) {
if (currentChar == '+' || currentChar == '#') {
//Shared name contains wildcard chars
return false;
}
if (lastChar == SHARED_SUBSCRIPTION_DELIMITER && currentChar == SHARED_SUBSCRIPTION_DELIMITER) {
//Check if the shared name is empty
return false;
}
}
// how many times did we see the sharedSubscriptionDelimiter?
if (isSharedSubscription && currentChar == SHARED_SUBSCRIPTION_DELIMITER) {
sharedSubscriptionDelimiterCharCount++;
}
// If the last character is a # and is prepended with /, then it's a valid subscription
if (i == length - 1 && currentChar == '#' && lastChar == '/') {
return true;
}
//Check if something follows after the # sign
if (lastChar == '#' || (currentChar == '#' && i == length - 1)) {
return false;
}
//Let's check if the + sign is in the middle of a string
if (currentChar == '+' && lastChar != '/') {
if (sharedSubscriptionDelimiterCharCount != 2 ||
!isSharedSubscription ||
lastChar != SHARED_SUBSCRIPTION_DELIMITER) {
return false;
}
}
//Let's check if the + sign is followed by a
if (lastChar == '+' && currentChar != '/') {
return false;
}
lastChar = currentChar;
}
// Is a shared subscription but the second delimiter (/) never came
return !isSharedSubscription || sharedSubscriptionDelimiterCharCount >= 2;
}
/**
* Checks if the topic starts with '$'.
*
* @param topic the topic to check
* @return true
if the topic starts with '$' false
otherwise
*/
public static boolean isDollarTopic(@NotNull final String topic) {
return topic.startsWith("$");
}
/**
* Check if a topic contains any wildcard character ('#','+').
*
* @param topic the topic to check
* @return true if it contains a wildcard character, else false.
*/
public static boolean containsWildcard(final String topic) {
return (topic.indexOf(MULTI_LEVEL_WILDCARD) != -1) || (topic.indexOf(SINGLE_LEVEL_WILDCARD) != -1);
}
/**
* Check a topic string if it is a shared subscription.
*
* @param topic the topic to check.
* @return the {@link SharedSubscription} for a given topic or if it is none.
*/
public static SharedSubscription checkForSharedSubscription(@NotNull final String topic) {
final Matcher matcher = SHARED_SUBSCRIPTION_PATTERN.matcher(topic);
if (matcher.matches()) {
final String shareGroup;
final String subscriptionTopic;
shareGroup = matcher.group(GROUP_INDEX);
subscriptionTopic = matcher.group(TOPIC_INDEX);
return new SharedSubscription(subscriptionTopic, shareGroup);
}
return null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy