blob: f0b97188ffbadbd84fd4c827d495db0de19f9888 [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.solr.core;
import javax.management.MBeanServer;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
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.nio.file.Paths;
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.stream.Collectors;
import com.google.common.base.Strings;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.solr.client.solrj.impl.HttpClientUtil;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.Utils;
import org.apache.solr.logging.LogWatcherConfig;
import org.apache.solr.metrics.reporters.SolrJmxReporter;
import org.apache.solr.update.UpdateShardHandlerConfig;
import org.apache.solr.common.util.DOMUtil;
import org.apache.solr.util.JmxUtil;
import org.apache.solr.common.util.PropertiesUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import static org.apache.solr.common.params.CommonParams.NAME;
/**
* 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 final static String ZK_HOST = "zkHost";
public final static String SOLR_XML_FILE = "solr.xml";
public final static String SOLR_DATA_HOME = "solr.data.home";
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
/**
* Given some node Properties, checks if non-null and a 'zkHost' is alread 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 corispond System property (if set)
*
* In theory we only need this logic once, ideally in SolrDispatchFilter, but we put it here to re-use
* redundently 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 && ! StringUtils.isEmpty(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 (! StringUtils.isEmpty(sysprop)) {
results.setProperty(ZK_HOST, sysprop);
}
return results;
}
public static NodeConfig fromConfig(Path solrHome, XmlConfigFile config) {
checkForIllegalConfig(config);
// 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(config.getSubstituteProperties()).getProperty(ZK_HOST);
CloudConfig cloudConfig = null;
UpdateShardHandlerConfig deprecatedUpdateConfig = null;
if (config.getNodeList("solr/solrcloud", false).getLength() > 0) {
NamedList<Object> cloudSection = readNodeListAsNamedList(config, "solr/solrcloud/*[@name]", "<solrcloud>");
deprecatedUpdateConfig = loadUpdateConfig(cloudSection, false);
cloudConfig = fillSolrCloudSection(cloudSection, config, defaultZkHost);
}
NamedList<Object> entries = readNodeListAsNamedList(config, "solr/*[@name]", "<solr>");
String nodeName = (String) entries.remove("nodeName");
if (Strings.isNullOrEmpty(nodeName) && cloudConfig != null)
nodeName = cloudConfig.getHost();
UpdateShardHandlerConfig updateConfig;
if (deprecatedUpdateConfig == null) {
updateConfig = loadUpdateConfig(readNodeListAsNamedList(config, "solr/updateshardhandler/*[@name]", "<updateshardhandler>"), true);
}
else {
updateConfig = loadUpdateConfig(readNodeListAsNamedList(config, "solr/updateshardhandler/*[@name]", "<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(config.getResourceLoader());
configBuilder.setUpdateShardHandlerConfig(updateConfig);
configBuilder.setShardHandlerFactoryConfig(getShardHandlerFactoryPluginInfo(config));
configBuilder.setSolrCoreCacheFactoryConfig(getTransientCoreCacheFactoryPluginInfo(config));
configBuilder.setTracerConfig(getTracerPluginInfo(config));
configBuilder.setLogWatcherConfig(loadLogWatcherConfig(config, "solr/logging/*[@name]", "solr/logging/watcher/*[@name]"));
configBuilder.setSolrProperties(loadProperties(config));
if (cloudConfig != null)
configBuilder.setCloudConfig(cloudConfig);
configBuilder.setBackupRepositoryPlugins(getBackupRepositoryPluginInfos(config));
configBuilder.setMetricsConfig(getMetricsConfig(config));
configBuilder.setDefaultZkHost(defaultZkHost);
return fillSolrSection(configBuilder, entries);
}
public static NodeConfig fromFile(Path solrHome, Path configFile, Properties substituteProps) {
log.info("Loading container configuration from {}", configFile);
if (!Files.exists(configFile)) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"solr.xml does not exist in " + configFile.getParent() + " cannot start Solr");
}
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) {
SolrResourceLoader loader = new SolrResourceLoader(solrHome);
if (substituteProps == null) {
substituteProps = new Properties();
}
try {
byte[] buf = IOUtils.toByteArray(is);
try (ByteArrayInputStream dup = new ByteArrayInputStream(buf)) {
XmlConfigFile config = new XmlConfigFile(loader, null, new InputSource(dup), null, substituteProps);
return fromConfig(solrHome, config);
}
} 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(XmlConfigFile config) {
failIfFound(config, "solr/@coreLoadThreads");
failIfFound(config, "solr/@persistent");
failIfFound(config, "solr/@sharedLib");
failIfFound(config, "solr/@zkHost");
failIfFound(config, "solr/cores");
assertSingleInstance("solrcloud", config);
assertSingleInstance("logging", config);
assertSingleInstance("logging/watcher", config);
assertSingleInstance("backup", config);
}
private static void assertSingleInstance(String section, XmlConfigFile config) {
if (config.getNodeList("/solr/" + section, false).getLength() > 1)
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Multiple instances of " + section + " section found in solr.xml");
}
private static void failIfFound(XmlConfigFile config, String xPath) {
if (config.getVal(xPath, false) != null) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Should not have found " + xPath +
"\n. Please upgrade your solr.xml: https://lucene.apache.org/solr/guide/format-of-solr-xml.html");
}
}
private static Properties loadProperties(XmlConfigFile config) {
try {
Node node = ((NodeList) config.evaluate("solr", XPathConstants.NODESET)).item(0);
XPath xpath = config.getXPath();
NodeList props = (NodeList) xpath.evaluate("property", node, XPathConstants.NODESET);
Properties properties = new Properties(config.getSubstituteProperties());
for (int i = 0; i < props.getLength(); i++) {
Node prop = props.item(i);
properties.setProperty(DOMUtil.getAttr(prop, NAME),
PropertiesUtil.substituteProperty(DOMUtil.getAttr(prop, "value"), null));
}
return properties;
}
catch (XPathExpressionException e) {
log.warn("Error parsing solr.xml: ", e);
return null;
}
}
private static NamedList<Object> readNodeListAsNamedList(XmlConfigFile config, String path, String section) {
NodeList nodes = config.getNodeList(path, false);
if (nodes == null) {
return null;
}
return checkForDuplicates(section, DOMUtil.nodesToNamedList(nodes));
}
private static NamedList<Object> checkForDuplicates(String section, NamedList<Object> nl) {
Set<String> keys = new HashSet<>();
for (Map.Entry<String, Object> 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, NamedList<Object> nl) {
for (Map.Entry<String, Object> entry : nl) {
String name = entry.getKey();
if (entry.getValue() == null)
continue;
String value = entry.getValue().toString();
switch (name) {
case "adminHandler":
builder.setCoreAdminHandlerClass(value);
break;
case "collectionsHandler":
builder.setCollectionsAdminHandlerClass(value);
break;
case "healthCheckHandler":
builder.setHealthCheckHandlerClass(value);
break;
case "infoHandler":
builder.setInfoHandlerClass(value);
break;
case "configSetsHandler":
builder.setConfigSetsHandlerClass(value);
break;
case "coreRootDirectory":
builder.setCoreRootDirectory(value);
break;
case "solrDataHome":
builder.setSolrDataHome(value);
break;
case "maxBooleanClauses":
builder.setBooleanQueryMaxClauseCount(parseInt(name, value));
break;
case "managementPath":
builder.setManagementPath(value);
break;
case "sharedLib":
builder.setSharedLibDirectory(value);
break;
case "allowPaths":
builder.setAllowPaths(stringToPaths(value));
break;
case "configSetBaseDir":
builder.setConfigSetBaseDirectory(value);
break;
case "shareSchema":
builder.setUseSchemaCache(Boolean.parseBoolean(value));
break;
case "coreLoadThreads":
builder.setCoreLoadThreads(parseInt(name, value));
break;
case "replayUpdatesThreads":
builder.setReplayUpdatesThreads(parseInt(name, value));
break;
case "transientCacheSize":
builder.setTransientCacheSize(parseInt(name, value));
break;
default:
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown configuration value in solr.xml: " + name);
}
}
return builder.build();
}
private static Set<Path> stringToPaths(String commaSeparatedString) {
if (Strings.isNullOrEmpty(commaSeparatedString)) {
return Collections.emptySet();
}
// Parse list of paths. The special value '*' is mapped to _ALL_ to mean all paths
return Arrays.stream(commaSeparatedString.split(",\\s?"))
.map(p -> Paths.get("*".equals(p) ? "_ALL_" : p)).collect(Collectors.toSet());
}
private static UpdateShardHandlerConfig loadUpdateConfig(NamedList<Object> 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<Object> 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<Object> nl, XmlConfigFile config, 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<String, Object> 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 "autoReplicaFailoverBadNodeExpiration": case "autoReplicaFailoverWorkLoopDelay":
//TODO remove this in Solr 8.0
log.info("Configuration parameter {} is ignored", name);
break;
case "autoReplicaFailoverWaitAfterExpiration":
builder.setAutoReplicaFailoverWaitAfterExpiration(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 "createCollectionWaitTimeTillActive":
builder.setCreateCollectionWaitTimeTillActive(parseInt(name, value));
break;
case "createCollectionCheckLeaderActive":
builder.setCreateCollectionCheckLeaderActive(Boolean.parseBoolean(value));
break;
default:
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown configuration parameter in <solrcloud> section of solr.xml: " + name);
}
}
return builder.build();
}
private static LogWatcherConfig loadLogWatcherConfig(XmlConfigFile config, String loggingPath, String watcherPath) {
String loggingClass = null;
boolean enabled = true;
int watcherQueueSize = 50;
String watcherThreshold = null;
for (Map.Entry<String, Object> entry : readNodeListAsNamedList(config, loggingPath, "<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<String, Object> entry : readNodeListAsNamedList(config, watcherPath, "<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);
}
private static PluginInfo getShardHandlerFactoryPluginInfo(XmlConfigFile config) {
Node node = config.getNode("solr/shardHandlerFactory", false);
return (node == null) ? null : new PluginInfo(node, "shardHandlerFactory", false, true);
}
private static PluginInfo[] getBackupRepositoryPluginInfos(XmlConfigFile config) {
NodeList nodes = (NodeList) config.evaluate("solr/backup/repository", XPathConstants.NODESET);
if (nodes == null || nodes.getLength() == 0)
return new PluginInfo[0];
PluginInfo[] configs = new PluginInfo[nodes.getLength()];
for (int i = 0; i < nodes.getLength(); i++) {
configs[i] = new PluginInfo(nodes.item(i), "BackupRepositoryFactory", true, true);
}
return configs;
}
private static MetricsConfig getMetricsConfig(XmlConfigFile config) {
MetricsConfig.MetricsConfigBuilder builder = new MetricsConfig.MetricsConfigBuilder();
Node node = config.getNode("solr/metrics", false);
// enabled by default
boolean enabled = true;
if (node != null) {
enabled = Boolean.parseBoolean(DOMUtil.getAttrOrDefault(node, "enabled", "true"));
}
builder.setEnabled(enabled);
if (!enabled) {
log.info("Metrics collection is disabled.");
return builder.build();
}
node = config.getNode("solr/metrics/suppliers/counter", false);
if (node != null) {
builder = builder.setCounterSupplier(new PluginInfo(node, "counterSupplier", false, false));
}
node = config.getNode("solr/metrics/suppliers/meter", false);
if (node != null) {
builder = builder.setMeterSupplier(new PluginInfo(node, "meterSupplier", false, false));
}
node = config.getNode("solr/metrics/suppliers/timer", false);
if (node != null) {
builder = builder.setTimerSupplier(new PluginInfo(node, "timerSupplier", false, false));
}
node = config.getNode("solr/metrics/suppliers/histogram", false);
if (node != null) {
builder = builder.setHistogramSupplier(new PluginInfo(node, "histogramSupplier", false, false));
}
node = config.getNode("solr/metrics/history", false);
if (node != null) {
builder = builder.setHistoryHandler(new PluginInfo(node, "history", false, false));
}
node = config.getNode("solr/metrics/missingValues", false);;
if (node != null) {
NamedList<Object> missingValues = DOMUtil.childNodesToNamedList(node);
builder.setNullNumber(decodeNullValue(missingValues.get("nullNumber")));
builder.setNotANumber(decodeNullValue(missingValues.get("notANumber")));
builder.setNullString(decodeNullValue(missingValues.get("nullString")));
builder.setNullObject(decodeNullValue(missingValues.get("nullObject")));
}
PluginInfo[] reporterPlugins = getMetricReporterPluginInfos(config);
Set<String> hiddenSysProps = getHiddenSysProps(config);
return builder
.setMetricReporterPlugins(reporterPlugins)
.setHiddenSysProps(hiddenSysProps)
.build();
}
private static Object decodeNullValue(Object o) {
if (o instanceof String) { // check if it's a JSON object
String str = (String) o;
if (!str.trim().isEmpty() && (str.startsWith("{") || str.startsWith("["))) {
try {
o = Utils.fromJSONString((String) o);
} catch (Exception e) {
// ignore
}
}
}
return o;
}
private static PluginInfo[] getMetricReporterPluginInfos(XmlConfigFile config) {
NodeList nodes = (NodeList) config.evaluate("solr/metrics/reporter", XPathConstants.NODESET);
List<PluginInfo> configs = new ArrayList<>();
boolean hasJmxReporter = false;
if (nodes != null && nodes.getLength() > 0) {
for (int i = 0; i < nodes.getLength(); i++) {
// we don't require class in order to support predefined replica and node reporter classes
PluginInfo info = new PluginInfo(nodes.item(i), "SolrMetricReporter", true, false);
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.info("MBean server found: {}, but no JMX reporters were configured - adding default JMX reporter.", mBeanServer);
Map<String,Object> 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[configs.size()]);
}
private static Set<String> getHiddenSysProps(XmlConfigFile config) {
NodeList nodes = (NodeList) config.evaluate("solr/metrics/hiddenSysProps/str", XPathConstants.NODESET);
if (nodes == null || nodes.getLength() == 0) {
return NodeConfig.NodeConfigBuilder.DEFAULT_HIDDEN_SYS_PROPS;
}
Set<String> props = new HashSet<>();
for (int i = 0; i < nodes.getLength(); i++) {
String prop = DOMUtil.getText(nodes.item(i));
if (prop != null && !prop.trim().isEmpty()) {
props.add(prop.trim());
}
}
if (props.isEmpty()) {
return NodeConfig.NodeConfigBuilder.DEFAULT_HIDDEN_SYS_PROPS;
} else {
return props;
}
}
private static PluginInfo getTransientCoreCacheFactoryPluginInfo(XmlConfigFile config) {
Node node = config.getNode("solr/transientCoreCacheFactory", false);
return (node == null) ? null : new PluginInfo(node, "transientCoreCacheFactory", false, true);
}
private static PluginInfo getTracerPluginInfo(XmlConfigFile config) {
Node node = config.getNode("solr/tracerConfig", false);
return (node == null) ? null : new PluginInfo(node, "tracerConfig", false, true);
}
}