/*
* 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.nifi.controller.cluster;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.util.FormatUtils;
import org.apache.nifi.util.NiFiProperties;
import org.apache.zookeeper.common.PathUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ZooKeeperClientConfig {
private static final Logger logger = LoggerFactory.getLogger(ZooKeeperClientConfig.class);
private static final Pattern PORT_PATTERN = Pattern.compile("[0-9]{1,5}");
private final String connectString;
private final int sessionTimeoutMillis;
private final int connectionTimeoutMillis;
private final String rootPath;
private ZooKeeperClientConfig(String connectString, int sessionTimeoutMillis, int connectionTimeoutMillis, String rootPath) {
this.connectString = connectString;
this.sessionTimeoutMillis = sessionTimeoutMillis;
this.connectionTimeoutMillis = connectionTimeoutMillis;
this.rootPath = rootPath.endsWith("/") ? rootPath.substring(0, rootPath.length() - 1) : rootPath;
}
public String getConnectString() {
return connectString;
}
public int getSessionTimeoutMillis() {
return sessionTimeoutMillis;
}
public int getConnectionTimeoutMillis() {
return connectionTimeoutMillis;
}
public String getRootPath() {
return rootPath;
}
public String resolvePath(final String path) {
if (path.startsWith("/")) {
return rootPath + path;
}
return rootPath + "/" + path;
}
public static ZooKeeperClientConfig createConfig(final NiFiProperties nifiProperties) {
final String connectString = nifiProperties.getProperty(NiFiProperties.ZOOKEEPER_CONNECT_STRING);
if (connectString == null || connectString.trim().isEmpty()) {
throw new IllegalStateException("The '" + NiFiProperties.ZOOKEEPER_CONNECT_STRING + "' property is not set in nifi.properties");
}
final String cleanedConnectString = cleanConnectString(connectString);
if (cleanedConnectString.isEmpty()) {
throw new IllegalStateException("The '" + NiFiProperties.ZOOKEEPER_CONNECT_STRING + "' property is set in nifi.properties but needs to be in pairs of host:port separated by commas");
}
final long sessionTimeoutMs = getTimePeriod(nifiProperties, NiFiProperties.ZOOKEEPER_SESSION_TIMEOUT, NiFiProperties.DEFAULT_ZOOKEEPER_SESSION_TIMEOUT);
final long connectionTimeoutMs = getTimePeriod(nifiProperties, NiFiProperties.ZOOKEEPER_CONNECT_TIMEOUT, NiFiProperties.DEFAULT_ZOOKEEPER_CONNECT_TIMEOUT);
final String rootPath = nifiProperties.getProperty(NiFiProperties.ZOOKEEPER_ROOT_NODE, NiFiProperties.DEFAULT_ZOOKEEPER_ROOT_NODE);
try {
PathUtils.validatePath(rootPath);
} catch (final IllegalArgumentException e) {
throw new IllegalArgumentException("The '" + NiFiProperties.ZOOKEEPER_ROOT_NODE + "' property in nifi.properties is set to an illegal value: " + rootPath);
}
return new ZooKeeperClientConfig(cleanedConnectString, (int) sessionTimeoutMs, (int) connectionTimeoutMs, rootPath);
}
private static int getTimePeriod(final NiFiProperties nifiProperties, final String propertyName, final String defaultValue) {
final String timeout = nifiProperties.getProperty(propertyName, defaultValue);
try {
return (int) FormatUtils.getTimeDuration(timeout, TimeUnit.MILLISECONDS);
} catch (final Exception e) {
logger.warn("Value of '" + propertyName + "' property is set to '" + timeout + "', which is not a valid time period. Using default of " + defaultValue);
return (int) FormatUtils.getTimeDuration(defaultValue, TimeUnit.MILLISECONDS);
}
}
/**
* Takes a given connect string and splits it by ',' character. For each
* split result trims whitespace then splits by ':' character. For each
* secondary split if a single value is returned it is trimmed and then the
* default zookeeper 2181 is append by adding ":2181". If two values are
* returned then the second value is evaluated to ensure it contains only
* digits and if not then the entry is in error and exception is raised.
* If more than two values are
* returned the entry is in error and an exception is raised.
* Each entry is trimmed and if empty the
* entry is skipped. After all splits are cleaned then they are all appended
* back together demarcated by "," and the full string is returned.
*
* @param connectString the string to clean
* @return cleaned connect string guaranteed to be non null but could be
* empty
* @throws IllegalStateException if any portions could not be cleaned/parsed
*/
public static String cleanConnectString(final String connectString) {
final String nospaces = StringUtils.deleteWhitespace(connectString);
final String hostPortPairs[] = StringUtils.split(nospaces, ",", 100);
final List<String> cleanedEntries = new ArrayList<>(hostPortPairs.length);
for (final String pair : hostPortPairs) {
final String pairSplits[] = StringUtils.split(pair, ":", 3);
if (pairSplits.length > 2 || pairSplits[0].isEmpty()) {
throw new IllegalStateException("Invalid host:port pair entry '" +
pair + "' in nifi.properties " + NiFiProperties.ZOOKEEPER_CONNECT_STRING + "' property");
}
if (pairSplits.length == 1) {
cleanedEntries.add(pairSplits[0] + ":2181");
}else{
if(PORT_PATTERN.matcher(pairSplits[1]).matches()){
cleanedEntries.add(pairSplits[0] + ":" + pairSplits[1]);
}else{
throw new IllegalStateException("The port specified in this pair must be 1 to 5 digits only but was '" +
pair + "' in nifi.properties " + NiFiProperties.ZOOKEEPER_CONNECT_STRING + "' property");
}
}
}
return StringUtils.join(cleanedEntries, ",");
}
}