Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.apache.solr.core.SolrXmlConfig Maven / Gradle / Ivy
/*
* 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.solr.core;
import static org.apache.solr.common.params.CommonParams.NAME;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.management.MBeanServer;
import org.apache.solr.client.solrj.impl.HttpClientUtil;
import org.apache.solr.cloud.ClusterSingleton;
import org.apache.solr.cluster.placement.PlacementPluginFactory;
import org.apache.solr.common.ConfigNode;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.CollectionUtil;
import org.apache.solr.common.util.DOMUtil;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.Utils;
import org.apache.solr.logging.LogWatcherConfig;
import org.apache.solr.metrics.reporters.SolrJmxReporter;
import org.apache.solr.search.CacheConfig;
import org.apache.solr.servlet.SolrDispatchFilter;
import org.apache.solr.update.UpdateShardHandlerConfig;
import org.apache.solr.util.DOMConfigNode;
import org.apache.solr.util.DataConfigNode;
import org.apache.solr.util.JmxUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;
/** Loads {@code solr.xml}. */
public class SolrXmlConfig {
// TODO should these from* methods return a NodeConfigBuilder so that the caller (a test) can make
// further manipulations like add properties and set the CorePropertiesLocator and "async" mode?
public static final String ZK_HOST = "zkHost";
public static final String SOLR_XML_FILE = "solr.xml";
public static final String SOLR_DATA_HOME = "solr.data.home";
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final Pattern COMMA_SEPARATED_PATTERN = Pattern.compile("\\s*,\\s*");
/**
* Given some node Properties, checks if non-null and a 'zkHost' is already included. If so, the
* Properties are returned as is. If not, then the returned value will be a new Properties,
* wrapping the original Properties, with the 'zkHost' value set based on the value of the
* corresponding System property (if set)
*
* In theory we only need this logic once, ideally in SolrDispatchFilter, but we put it here to
* re-use redundantly because of how much surface area our API has for various tests to poke at
* us.
*/
public static Properties wrapAndSetZkHostFromSysPropIfNeeded(final Properties props) {
if (null != props && StrUtils.isNotNullOrEmpty(props.getProperty(ZK_HOST))) {
// nothing to do...
return props;
}
// we always wrap if we might set a property -- never mutate the original props
final Properties results = (null == props ? new Properties() : new Properties(props));
final String sysprop = System.getProperty(ZK_HOST);
if (StrUtils.isNotNullOrEmpty(sysprop)) {
results.setProperty(ZK_HOST, sysprop);
}
return results;
}
public static NodeConfig fromConfig(
Path solrHome,
Properties substituteProperties,
boolean fromZookeeper,
ConfigNode root,
SolrResourceLoader loader) {
checkForIllegalConfig(root);
// sanity check: if our config came from zookeeper, then there *MUST* be Node Properties that
// tell us what zkHost was used to read it (either via webapp context attribute, or that
// SolrDispatchFilter filled in for us from system properties)
assert ((!fromZookeeper)
|| (null != substituteProperties && null != substituteProperties.getProperty(ZK_HOST)));
// Regardless of where/how we this XmlConfigFile was loaded from, if it contains a zkHost
// property, we're going to use that as our "default" and only *directly* check the system
// property if it's not specified.
//
// (checking the sys prop here is really just for tests that by-pass SolrDispatchFilter. In
// non-test situations, SolrDispatchFilter will check the system property if needed in order to
// try and load solr.xml from ZK, and should have put the sys prop value in the node properties
// for us)
final String defaultZkHost =
wrapAndSetZkHostFromSysPropIfNeeded(substituteProperties).getProperty(ZK_HOST);
CloudConfig cloudConfig = null;
UpdateShardHandlerConfig deprecatedUpdateConfig = null;
if (root.get("solrcloud").exists()) {
NamedList cloudSection =
readNodeListAsNamedList(root.get("solrcloud"), "");
deprecatedUpdateConfig = loadUpdateConfig(cloudSection, false);
cloudConfig = fillSolrCloudSection(cloudSection, defaultZkHost);
}
NamedList entries = readNodeListAsNamedList(root, "");
String nodeName = (String) entries.remove("nodeName");
if (StrUtils.isNullOrEmpty(nodeName) && cloudConfig != null) nodeName = cloudConfig.getHost();
// It should go inside the fillSolrSection method but
// since it is arranged as a separate section it is placed here
Map coreAdminHandlerActions =
readNodeListAsNamedList(root.get("coreAdminHandlerActions"), "")
.asMap()
.entrySet()
.stream()
.collect(Collectors.toMap(item -> item.getKey(), item -> item.getValue().toString()));
UpdateShardHandlerConfig updateConfig;
if (deprecatedUpdateConfig == null) {
updateConfig =
loadUpdateConfig(
readNodeListAsNamedList(root.get("updateshardhandler"), ""),
true);
} else {
updateConfig =
loadUpdateConfig(
readNodeListAsNamedList(root.get("updateshardhandler"), ""),
false);
if (updateConfig != null) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"UpdateShardHandler configuration defined twice in solr.xml");
}
updateConfig = deprecatedUpdateConfig;
}
NodeConfig.NodeConfigBuilder configBuilder =
new NodeConfig.NodeConfigBuilder(nodeName, solrHome);
configBuilder.setSolrResourceLoader(loader);
configBuilder.setUpdateShardHandlerConfig(updateConfig);
configBuilder.setShardHandlerFactoryConfig(getPluginInfo(root.get("shardHandlerFactory")));
configBuilder.setTracerConfig(getPluginInfo(root.get("tracerConfig")));
configBuilder.setLogWatcherConfig(loadLogWatcherConfig(root.get("logging")));
configBuilder.setSolrProperties(loadProperties(root, substituteProperties));
if (cloudConfig != null) configBuilder.setCloudConfig(cloudConfig);
configBuilder.setBackupRepositoryPlugins(
getBackupRepositoryPluginInfos(root.get("backup").getAll("repository")));
configBuilder.setClusterPlugins(getClusterPlugins(loader, root));
// will be removed in Solr 10, but until then, use it if a
// is not provided under .
// Remove this line in 10.0
configBuilder.setHiddenSysProps(getHiddenSysProps(root.get("metrics")));
configBuilder.setMetricsConfig(getMetricsConfig(root.get("metrics")));
configBuilder.setCachesConfig(getCachesConfig(loader, root.get("caches")));
configBuilder.setFromZookeeper(fromZookeeper);
configBuilder.setDefaultZkHost(defaultZkHost);
configBuilder.setCoreAdminHandlerActions(coreAdminHandlerActions);
return fillSolrSection(configBuilder, root);
}
public static NodeConfig fromFile(Path solrHome, Path configFile, Properties substituteProps) {
if (!Files.exists(configFile)) {
if (Boolean.getBoolean("solr.solrxml.required")) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"solr.xml does not exist in " + configFile.getParent() + " cannot start Solr");
}
log.info("solr.xml not found in SOLR_HOME, using built-in default");
String solrInstallDir = System.getProperty(SolrDispatchFilter.SOLR_INSTALL_DIR_ATTRIBUTE);
if (solrInstallDir == null) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"Could not find default solr.xml file due to missing "
+ SolrDispatchFilter.SOLR_INSTALL_DIR_ATTRIBUTE);
}
configFile = Path.of(solrInstallDir).resolve("server").resolve("solr").resolve("solr.xml");
}
log.info("Loading solr.xml from {}", configFile);
try (InputStream inputStream = Files.newInputStream(configFile)) {
return fromInputStream(solrHome, inputStream, substituteProps);
} catch (SolrException exc) {
throw exc;
} catch (Exception exc) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR, "Could not load SOLR configuration", exc);
}
}
/** TEST-ONLY */
public static NodeConfig fromString(Path solrHome, String xml) {
return fromInputStream(
solrHome, new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)), new Properties());
}
public static NodeConfig fromInputStream(
Path solrHome, InputStream is, Properties substituteProps) {
return fromInputStream(solrHome, is, substituteProps, false);
}
public static NodeConfig fromInputStream(
Path solrHome, InputStream is, Properties substituteProps, boolean fromZookeeper) {
SolrResourceLoader loader = new SolrResourceLoader(solrHome);
if (substituteProps == null) {
substituteProps = new Properties();
}
try {
byte[] buf = is.readAllBytes();
try (ByteArrayInputStream dup = new ByteArrayInputStream(buf)) {
XmlConfigFile config =
new XmlConfigFile(loader, null, new InputSource(dup), null, substituteProps);
return fromConfig(
solrHome,
substituteProps,
fromZookeeper,
new DataConfigNode(new DOMConfigNode(config.getDocument().getDocumentElement())),
loader);
}
} catch (SolrException exc) {
throw exc;
} catch (Exception e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
}
}
public static NodeConfig fromSolrHome(Path solrHome, Properties substituteProps) {
return fromFile(solrHome, solrHome.resolve(SOLR_XML_FILE), substituteProps);
}
private static void checkForIllegalConfig(ConfigNode root) {
failIfFound(root.attr("coreLoadThreads"), "solr/@coreLoadThreads");
failIfFound(root.attr("persistent"), "solr/@persistent");
failIfFound(root.attr("sharedLib"), "solr/@sharedLib");
failIfFound(root.attr("zkHost"), "solr/@zkHost");
failIfFound(root.attr("zkHost"), "solr/@zkHost");
failIfFound(root.get("cores").exists() ? "" : null, "solr/cores");
assertSingleInstance(root.getAll("solrcloud"), "solrcloud");
assertSingleInstance(root.getAll("logging"), "logging");
assertSingleInstance(root.get("logging").getAll("watcher"), "logging/watcher");
assertSingleInstance(root.getAll("backup"), "backup");
assertSingleInstance(root.getAll("coreAdminHandlerActions"), "coreAdminHandlerActions");
}
private static void assertSingleInstance(List l, String section) {
if (l.size() > 1)
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"Multiple instances of " + section + " section found in solr.xml");
}
private static void failIfFound(String val, String xPath) {
if (val != null) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"Should not have found "
+ xPath
+ "\n. Please upgrade your solr.xml: https://solr.apache.org/guide/solr/latest/configuration-guide/configuring-solr-xml.html");
}
}
private static Properties loadProperties(ConfigNode cfg, Properties substituteProperties) {
Properties properties = new Properties(substituteProperties);
cfg.forEachChild(
it -> {
if (it.name().equals("property")) {
properties.setProperty(it.attr(NAME), it.attr("value"));
}
return Boolean.TRUE;
});
return properties;
}
private static NamedList readNodeListAsNamedList(ConfigNode cfg, String section) {
NamedList nl = DOMUtil.readNamedListChildren(cfg);
Set keys = new HashSet<>();
for (Map.Entry entry : nl) {
if (!keys.add(entry.getKey()))
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
section + " section of solr.xml contains duplicated '" + entry.getKey() + "'");
}
return nl;
}
private static int parseInt(String field, String value) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"Error parsing '" + field + "', value '" + value + "' cannot be parsed as int");
}
}
private static NodeConfig fillSolrSection(NodeConfig.NodeConfigBuilder builder, ConfigNode root) {
forEachNamedListEntry(
root,
it -> {
if (it.name().equals("null")) return;
try {
switch (it.attr(NAME)) {
case "adminHandler":
builder.setCoreAdminHandlerClass(it.txt());
break;
case "collectionsHandler":
builder.setCollectionsAdminHandlerClass(it.txt());
break;
case "healthCheckHandler":
builder.setHealthCheckHandlerClass(it.txt());
break;
case "infoHandler":
builder.setInfoHandlerClass(it.txt());
break;
case "configSetsHandler":
builder.setConfigSetsHandlerClass(it.txt());
break;
case "configSetService":
builder.setConfigSetServiceClass(it.txt());
break;
case "coresLocator":
builder.setCoresLocatorClass(it.txt());
break;
case "coreSorter":
builder.setCoreSorterClass(it.txt());
break;
case "coreRootDirectory":
builder.setCoreRootDirectory(it.txt());
break;
case "solrDataHome":
builder.setSolrDataHome(it.txt());
break;
case "maxBooleanClauses":
builder.setBooleanQueryMaxClauseCount(it.intVal(-1));
break;
case "managementPath":
builder.setManagementPath(it.txt());
break;
case "sharedLib":
builder.setSharedLibDirectory(it.txt());
break;
case "modules":
builder.setModules(it.txt());
break;
case "hiddenSysProps":
builder.setHiddenSysProps(it.txt());
break;
case "allowPaths":
builder.setAllowPaths(separatePaths(it.txt()));
break;
case "hideStackTrace":
builder.setHideStackTrace(it.boolVal(false));
break;
case "configSetBaseDir":
builder.setConfigSetBaseDirectory(it.txt());
break;
case "shareSchema":
builder.setUseSchemaCache(it.boolVal(false));
break;
case "coreLoadThreads":
builder.setCoreLoadThreads(it.intVal(-1));
break;
case "replayUpdatesThreads":
builder.setReplayUpdatesThreads(it.intVal(-1));
break;
case "transientCacheSize":
log.warn("solr.xml transientCacheSize -- transient cores is deprecated");
builder.setTransientCacheSize(it.intVal(-1));
break;
case "allowUrls":
builder.setAllowUrls(separateStrings(it.txt()));
break;
default:
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"Unknown configuration value in solr.xml: " + it.attr(NAME));
}
} catch (NumberFormatException e) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"Error parsing '" + it.attr(NAME) + "', value '" + it.txt() + "' cannot be parsed");
}
});
return builder.build();
}
private static List separateStrings(String commaSeparatedString) {
if (StrUtils.isNullOrEmpty(commaSeparatedString)) {
return Collections.emptyList();
}
return Arrays.asList(COMMA_SEPARATED_PATTERN.split(commaSeparatedString));
}
private static Set separateStringsToSet(String commaSeparatedString) {
if (StrUtils.isNullOrEmpty(commaSeparatedString)) {
return Collections.emptySet();
}
return Set.of(COMMA_SEPARATED_PATTERN.split(commaSeparatedString));
}
private static Set separatePaths(String commaSeparatedString) {
if (StrUtils.isNullOrEmpty(commaSeparatedString)) {
return Collections.emptySet();
}
// Parse the list of paths. The special values '*' and '_ALL_' mean all paths.
String[] pathStrings = COMMA_SEPARATED_PATTERN.split(commaSeparatedString);
SolrPaths.AllowPathBuilder allowPathBuilder = new SolrPaths.AllowPathBuilder();
for (String p : pathStrings) {
allowPathBuilder.addPath(p);
}
return allowPathBuilder.build();
}
private static UpdateShardHandlerConfig loadUpdateConfig(
NamedList nl, boolean alwaysDefine) {
if (nl == null && !alwaysDefine) return null;
if (nl == null) return UpdateShardHandlerConfig.DEFAULT;
boolean defined = false;
int maxUpdateConnections = HttpClientUtil.DEFAULT_MAXCONNECTIONS;
int maxUpdateConnectionsPerHost = HttpClientUtil.DEFAULT_MAXCONNECTIONSPERHOST;
int distributedSocketTimeout = HttpClientUtil.DEFAULT_SO_TIMEOUT;
int distributedConnectionTimeout = HttpClientUtil.DEFAULT_CONNECT_TIMEOUT;
String metricNameStrategy = UpdateShardHandlerConfig.DEFAULT_METRICNAMESTRATEGY;
int maxRecoveryThreads = UpdateShardHandlerConfig.DEFAULT_MAXRECOVERYTHREADS;
Object muc = nl.remove("maxUpdateConnections");
if (muc != null) {
maxUpdateConnections = parseInt("maxUpdateConnections", muc.toString());
defined = true;
}
Object mucph = nl.remove("maxUpdateConnectionsPerHost");
if (mucph != null) {
maxUpdateConnectionsPerHost = parseInt("maxUpdateConnectionsPerHost", mucph.toString());
defined = true;
}
Object dst = nl.remove("distribUpdateSoTimeout");
if (dst != null) {
distributedSocketTimeout = parseInt("distribUpdateSoTimeout", dst.toString());
defined = true;
}
Object dct = nl.remove("distribUpdateConnTimeout");
if (dct != null) {
distributedConnectionTimeout = parseInt("distribUpdateConnTimeout", dct.toString());
defined = true;
}
Object mns = nl.remove("metricNameStrategy");
if (mns != null) {
metricNameStrategy = mns.toString();
defined = true;
}
Object mrt = nl.remove("maxRecoveryThreads");
if (mrt != null) {
maxRecoveryThreads = parseInt("maxRecoveryThreads", mrt.toString());
defined = true;
}
if (!defined && !alwaysDefine) return null;
return new UpdateShardHandlerConfig(
maxUpdateConnections,
maxUpdateConnectionsPerHost,
distributedSocketTimeout,
distributedConnectionTimeout,
metricNameStrategy,
maxRecoveryThreads);
}
private static String removeValue(NamedList nl, String key) {
Object value = nl.remove(key);
if (value == null) return null;
return value.toString();
}
private static String required(String section, String key, String value) {
if (value != null) return value;
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
section + " section missing required entry '" + key + "'");
}
private static CloudConfig fillSolrCloudSection(NamedList nl, String defaultZkHost) {
int hostPort =
parseInt("hostPort", required("solrcloud", "hostPort", removeValue(nl, "hostPort")));
if (hostPort <= 0) {
// Default to the port that jetty is listening on, or 8983 if that is not provided.
hostPort = parseInt("jetty.port", System.getProperty("jetty.port", "8983"));
}
String hostName = required("solrcloud", "host", removeValue(nl, "host"));
String hostContext = required("solrcloud", "hostContext", removeValue(nl, "hostContext"));
CloudConfig.CloudConfigBuilder builder =
new CloudConfig.CloudConfigBuilder(hostName, hostPort, hostContext);
// set the defaultZkHost until/unless it's overridden in the "cloud section" (below)...
builder.setZkHost(defaultZkHost);
for (Map.Entry entry : nl) {
String name = entry.getKey();
if (entry.getValue() == null) continue;
String value = entry.getValue().toString();
switch (name) {
case "leaderVoteWait":
builder.setLeaderVoteWait(parseInt(name, value));
break;
case "leaderConflictResolveWait":
builder.setLeaderConflictResolveWait(parseInt(name, value));
break;
case "zkClientTimeout":
builder.setZkClientTimeout(parseInt(name, value));
break;
case "zkHost":
builder.setZkHost(value);
break;
case "genericCoreNodeNames":
builder.setUseGenericCoreNames(Boolean.parseBoolean(value));
break;
case "zkACLProvider":
builder.setZkACLProviderClass(value);
break;
case "zkCredentialsProvider":
builder.setZkCredentialsProviderClass(value);
break;
case "zkCredentialsInjector":
builder.setZkCredentialsInjectorClass(value);
break;
case "createCollectionWaitTimeTillActive":
builder.setCreateCollectionWaitTimeTillActive(parseInt(name, value));
break;
case "createCollectionCheckLeaderActive":
builder.setCreateCollectionCheckLeaderActive(Boolean.parseBoolean(value));
break;
case "pkiHandlerPrivateKeyPath":
builder.setPkiHandlerPrivateKeyPath(value);
break;
case "pkiHandlerPublicKeyPath":
builder.setPkiHandlerPublicKeyPath(value);
break;
case "distributedClusterStateUpdates":
builder.setUseDistributedClusterStateUpdates(Boolean.parseBoolean(value));
break;
case "distributedCollectionConfigSetExecution":
builder.setUseDistributedCollectionConfigSetExecution(Boolean.parseBoolean(value));
break;
case "minStateByteLenForCompression":
builder.setMinStateByteLenForCompression(parseInt(name, value));
break;
case "stateCompressor":
builder.setStateCompressorClass(value);
break;
default:
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"Unknown configuration parameter in section of solr.xml: " + name);
}
}
return builder.build();
}
private static LogWatcherConfig loadLogWatcherConfig(ConfigNode logging) {
String loggingClass = null;
boolean enabled = true;
int watcherQueueSize = 50;
String watcherThreshold = null;
for (Map.Entry entry : readNodeListAsNamedList(logging, "")) {
String name = entry.getKey();
String value = entry.getValue().toString();
switch (name) {
case "class":
loggingClass = value;
break;
case "enabled":
enabled = Boolean.parseBoolean(value);
break;
default:
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR, "Unknown value in logwatcher config: " + name);
}
}
for (Map.Entry entry :
readNodeListAsNamedList(logging.get("watcher"), "")) {
String name = entry.getKey();
String value = entry.getValue().toString();
switch (name) {
case "size":
watcherQueueSize = parseInt(name, value);
break;
case "threshold":
watcherThreshold = value;
break;
default:
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR, "Unknown value in logwatcher config: " + name);
}
}
return new LogWatcherConfig(enabled, loggingClass, watcherThreshold, watcherQueueSize);
}
static void forEachNamedListEntry(ConfigNode cfg, Consumer fun) {
cfg.forEachChild(
it -> {
if (DOMUtil.NL_TAGS.contains(it.name())) {
fun.accept(it);
}
return Boolean.TRUE;
});
}
private static PluginInfo[] getBackupRepositoryPluginInfos(List cfg) {
return cfg.stream()
.map(c -> new PluginInfo(c, "BackupRepositoryFactory", true, true))
.filter(PluginInfo::isEnabled)
.toArray(PluginInfo[]::new);
}
private static PluginInfo[] getClusterPlugins(SolrResourceLoader loader, ConfigNode root) {
List clusterPlugins = new ArrayList<>();
Collections.addAll(
clusterPlugins, getClusterSingletonPluginInfos(loader, root.getAll("clusterSingleton")));
PluginInfo replicaPlacementFactory = getPluginInfo(root.get("replicaPlacementFactory"));
if (replicaPlacementFactory != null) {
if (replicaPlacementFactory.name != null
&& !replicaPlacementFactory.name.equals(PlacementPluginFactory.PLUGIN_NAME)) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"The replicaPlacementFactory name attribute must be "
+ PlacementPluginFactory.PLUGIN_NAME);
}
clusterPlugins.add(replicaPlacementFactory);
}
return clusterPlugins.toArray(new PluginInfo[0]);
}
private static PluginInfo[] getClusterSingletonPluginInfos(
SolrResourceLoader loader, List nodes) {
if (nodes == null || nodes.isEmpty()) {
return new PluginInfo[0];
}
List plugins =
nodes.stream()
.map(n -> new PluginInfo(n, n.name(), true, true))
.filter(PluginInfo::isEnabled)
.collect(Collectors.toList());
// Cluster plugin names must be unique
Set names = CollectionUtil.newHashSet(nodes.size());
Set duplicateNames =
plugins.stream()
.filter(p -> !names.add(p.name))
.map(p -> p.name)
.collect(Collectors.toSet());
if (!duplicateNames.isEmpty()) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"Multiple clusterSingleton sections with name '"
+ String.join("', '", duplicateNames)
+ "' found in solr.xml");
}
try {
plugins.forEach(
p -> {
loader.findClass(p.className, ClusterSingleton.class);
});
} catch (ClassCastException e) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"clusterSingleton plugins must implement the interface "
+ ClusterSingleton.class.getName());
}
return plugins.toArray(new PluginInfo[0]);
}
private static MetricsConfig getMetricsConfig(ConfigNode metrics) {
MetricsConfig.MetricsConfigBuilder builder = new MetricsConfig.MetricsConfigBuilder();
boolean enabled = metrics.boolAttr("enabled", true);
builder.setEnabled(enabled);
if (!enabled) {
log.info("Metrics collection is disabled.");
return builder.build();
}
builder.setCounterSupplier(getPluginInfo(metrics.get("suppliers").get("counter")));
builder.setMeterSupplier(getPluginInfo(metrics.get("suppliers").get("meter")));
builder.setTimerSupplier(getPluginInfo(metrics.get("suppliers").get("timer")));
builder.setHistogramSupplier(getPluginInfo(metrics.get("suppliers").get("histogram")));
if (metrics.get("missingValues").exists()) {
NamedList missingValues = DOMUtil.childNodesToNamedList(metrics.get("missingValues"));
builder.setNullNumber(decodeNullValue(missingValues.get("nullNumber")));
builder.setNotANumber(decodeNullValue(missingValues.get("notANumber")));
builder.setNullString(decodeNullValue(missingValues.get("nullString")));
builder.setNullObject(decodeNullValue(missingValues.get("nullObject")));
}
ConfigNode caching = metrics.get("solr/metrics/caching");
if (caching != null) {
Object threadsCachingIntervalSeconds =
DOMUtil.childNodesToNamedList(caching).get("threadsIntervalSeconds", null);
builder.setCacheConfig(
new MetricsConfig.CacheConfig(
threadsCachingIntervalSeconds == null
? null
: Integer.parseInt(threadsCachingIntervalSeconds.toString())));
}
PluginInfo[] reporterPlugins = getMetricReporterPluginInfos(metrics);
return builder.setMetricReporterPlugins(reporterPlugins).build();
}
private static Map getCachesConfig(
SolrResourceLoader loader, ConfigNode caches) {
Map ret =
CacheConfig.getMultipleConfigs(loader, null, null, caches.getAll("cache"));
for (CacheConfig c : ret.values()) {
if (c.getRegenerator() != null) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"node-level caches should not be configured with a regenerator!");
}
}
return Collections.unmodifiableMap(ret);
}
private static Object decodeNullValue(Object o) {
if (o instanceof String) { // check if it's a JSON object
String str = (String) o;
if (!str.isBlank() && (str.startsWith("{") || str.startsWith("["))) {
try {
o = Utils.fromJSONString((String) o);
} catch (Exception e) {
// ignore
}
}
}
return o;
}
private static PluginInfo[] getMetricReporterPluginInfos(ConfigNode metrics) {
List configs = new ArrayList<>();
boolean hasJmxReporter = false;
for (ConfigNode node : metrics.getAll("reporter")) {
PluginInfo info = getPluginInfo(node);
if (info == null) {
continue;
}
String clazz = info.className;
if (clazz != null && clazz.equals(SolrJmxReporter.class.getName())) {
hasJmxReporter = true;
}
configs.add(info);
}
// if there's an MBean server running but there was no JMX reporter then add a default one
MBeanServer mBeanServer = JmxUtil.findFirstMBeanServer();
if (mBeanServer != null && !hasJmxReporter) {
log.debug(
"MBean server found: {}, but no JMX reporters were configured - adding default JMX reporter.",
mBeanServer);
Map attributes = new HashMap<>();
attributes.put("name", "default");
attributes.put("class", SolrJmxReporter.class.getName());
PluginInfo defaultPlugin = new PluginInfo("reporter", attributes);
configs.add(defaultPlugin);
}
return configs.toArray(new PluginInfo[0]);
}
/**
* Deprecated as of 9.3, will be removed in 10.0
*
* @param metrics configNode for the metrics
* @return a comma-separated list of hidden Sys Props
*/
@Deprecated(forRemoval = true, since = "9.3")
private static String getHiddenSysProps(ConfigNode metrics) {
ConfigNode p = metrics.get("hiddenSysProps");
if (!p.exists()) return null;
Set props = new HashSet<>();
p.forEachChild(
it -> {
if (it.name().equals("str") && StrUtils.isNotNullOrEmpty(it.txt()))
props.add(Pattern.quote(it.txt()));
return Boolean.TRUE;
});
return String.join(",", props);
}
private static PluginInfo getPluginInfo(ConfigNode cfg) {
if (cfg == null || !cfg.exists()) return null;
final var pluginInfo = new PluginInfo(cfg, cfg.name(), false, true);
return pluginInfo.isEnabled() ? pluginInfo : null;
}
}