From 61967d99a4e1bc910c03c83d747e84efcaf39d41 Mon Sep 17 00:00:00 2001 From: "Chris M. Hostetter" Date: Mon, 21 Jul 2014 22:18:38 +0000 Subject: [PATCH] SOLR-5746: Bugs in solr.xml parsing have been fixed to more correctly deal with the various datatypes of options people can specify, additional error handling of duplicated/unidentified options has also been added git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1612419 13f79535-47bb-0310-9956-ffa450edef68 --- solr/CHANGES.txt | 12 + .../java/org/apache/solr/core/ConfigSolr.java | 119 +++---- .../org/apache/solr/core/ConfigSolrXml.java | 228 ++++++++++--- .../apache/solr/core/ConfigSolrXmlOld.java | 124 ++++--- solr/core/src/test-files/solr/solr-50-all.xml | 4 +- .../apache/solr/cloud/SolrXmlInZkTest.java | 2 +- .../org/apache/solr/core/TestSolrXml.java | 315 ++++++++++++++---- 7 files changed, 568 insertions(+), 236 deletions(-) diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index e975644c8f5..e69d9aea1d0 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -114,6 +114,13 @@ Upgrading from Solr 4.9 * CoreContainer.remove() has been removed. You should now use CoreContainer.unload() to delete a SolrCore (see SOLR-6232). +* solr.xml parsing has been improved to better account for the expected data types of + various options. As part of this fix, additional error checking has also been added to + provide errors in the event of duplicated options, or unknown option names that may + indicate a typo. Users who have modified their solr.xml in the past and now upgrade may + get errors on startup if they have typos or unexpected options specified in their solr.xml + file. (See SOLR-5746 for more information.) + Detailed Change List ---------------------- @@ -205,6 +212,11 @@ Bug Fixes ArrayIndexOutOfBoundsException when using the composite id router. (Steve Rowe) +* SOLR-5746: Bugs in solr.xml parsing have been fixed to more correctly deal with the various + datatypes of options people can specify, additional error handling of duplicated/unidentified + options has also been added. (Maciej Zasada, hossman) + + Optimizations --------------------- diff --git a/solr/core/src/java/org/apache/solr/core/ConfigSolr.java b/solr/core/src/java/org/apache/solr/core/ConfigSolr.java index dae776ddc1f..b080244ca82 100644 --- a/solr/core/src/java/org/apache/solr/core/ConfigSolr.java +++ b/solr/core/src/java/org/apache/solr/core/ConfigSolr.java @@ -17,7 +17,17 @@ package org.apache.solr.core; * limitations under the License. */ -import com.google.common.io.ByteStreams; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; import org.apache.commons.io.IOUtils; import org.apache.solr.cloud.CloudConfigSetService; @@ -32,20 +42,6 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpressionException; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - public abstract class ConfigSolr { protected static Logger log = LoggerFactory.getLogger(ConfigSolr.class); @@ -55,24 +51,18 @@ public abstract class ConfigSolr { public static ConfigSolr fromFile(SolrResourceLoader loader, File configFile) { log.info("Loading container configuration from {}", configFile.getAbsolutePath()); - InputStream inputStream = null; - - try { - if (!configFile.exists()) { - throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, - "solr.xml does not exist in " + configFile.getAbsolutePath() + " cannot start Solr"); - } - else { - inputStream = new FileInputStream(configFile); - } - return fromInputStream(loader, inputStream); - } - catch (Exception e) { + if (!configFile.exists()) { throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, - "Could not load SOLR configuration", e); + "solr.xml does not exist in " + configFile.getAbsolutePath() + " cannot start Solr"); } - finally { - IOUtils.closeQuietly(inputStream); + + try (InputStream inputStream = new FileInputStream(configFile)) { + return fromInputStream(loader, inputStream); + } catch (SolrException exc) { + throw exc; + } catch (Exception exc) { + throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, + "Could not load SOLR configuration", exc); } } @@ -84,9 +74,12 @@ public abstract class ConfigSolr { try { byte[] buf = IOUtils.toByteArray(is); String originalXml = new String(buf, StandardCharsets.UTF_8); - ByteArrayInputStream dup = new ByteArrayInputStream(buf); - Config config = new Config(loader, null, new InputSource(dup), null, false); - return fromConfig(config, originalXml); + try (ByteArrayInputStream dup = new ByteArrayInputStream(buf)) { + Config config = new Config(loader, null, new InputSource(dup), null, false); + return fromConfig(config, originalXml); + } + } catch (SolrException exc) { + throw exc; } catch (Exception e) { throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e); } @@ -133,7 +126,7 @@ public abstract class ConfigSolr { String sysProp = System.getProperty("zkClientTimeout"); if (sysProp != null) return Integer.parseInt(sysProp); - return getInt(CfgProp.SOLR_ZKCLIENTTIMEOUT, DEFAULT_ZK_CLIENT_TIMEOUT); + return get(CfgProp.SOLR_ZKCLIENTTIMEOUT, DEFAULT_ZK_CLIENT_TIMEOUT); } private static final int DEFAULT_ZK_CLIENT_TIMEOUT = 15000; @@ -156,35 +149,35 @@ public abstract class ConfigSolr { } public int getLeaderVoteWait() { - return getInt(CfgProp.SOLR_LEADERVOTEWAIT, DEFAULT_LEADER_VOTE_WAIT); + return get(CfgProp.SOLR_LEADERVOTEWAIT, DEFAULT_LEADER_VOTE_WAIT); } public int getLeaderConflictResolveWait() { - return getInt(CfgProp.SOLR_LEADERCONFLICTRESOLVEWAIT, DEFAULT_LEADER_CONFLICT_RESOLVE_WAIT); + return get(CfgProp.SOLR_LEADERCONFLICTRESOLVEWAIT, DEFAULT_LEADER_CONFLICT_RESOLVE_WAIT); } public boolean getGenericCoreNodeNames() { - return getBool(CfgProp.SOLR_GENERICCORENODENAMES, false); + return get(CfgProp.SOLR_GENERICCORENODENAMES, false); } public int getDistributedConnectionTimeout() { - return getInt(CfgProp.SOLR_DISTRIBUPDATECONNTIMEOUT, 0); + return get(CfgProp.SOLR_DISTRIBUPDATECONNTIMEOUT, 0); } public int getDistributedSocketTimeout() { - return getInt(CfgProp.SOLR_DISTRIBUPDATESOTIMEOUT, 0); + return get(CfgProp.SOLR_DISTRIBUPDATESOTIMEOUT, 0); } public int getMaxUpdateConnections() { - return getInt(CfgProp.SOLR_MAXUPDATECONNECTIONS, 10000); + return get(CfgProp.SOLR_MAXUPDATECONNECTIONS, 10000); } public int getMaxUpdateConnectionsPerHost() { - return getInt(CfgProp.SOLR_MAXUPDATECONNECTIONSPERHOST, 100); + return get(CfgProp.SOLR_MAXUPDATECONNECTIONSPERHOST, 100); } public int getCoreLoadThreadCount() { - return getInt(ConfigSolr.CfgProp.SOLR_CORELOADTHREADS, DEFAULT_CORE_LOAD_THREADS); + return get(ConfigSolr.CfgProp.SOLR_CORELOADTHREADS, DEFAULT_CORE_LOAD_THREADS); } public String getSharedLibDirectory() { @@ -214,7 +207,7 @@ public abstract class ConfigSolr { } public boolean hasSchemaCache() { - return getBool(ConfigSolr.CfgProp.SOLR_SHARESCHEMA, false); + return get(ConfigSolr.CfgProp.SOLR_SHARESCHEMA, false); } public String getManagementPath() { @@ -226,16 +219,18 @@ public abstract class ConfigSolr { } public LogWatcherConfig getLogWatcherConfig() { + String loggingClass = get(CfgProp.SOLR_LOGGING_CLASS, null); + String loggingWatcherThreshold = get(CfgProp.SOLR_LOGGING_WATCHER_THRESHOLD, null); return new LogWatcherConfig( - getBool(CfgProp.SOLR_LOGGING_ENABLED, true), - get(CfgProp.SOLR_LOGGING_CLASS, null), - get(CfgProp.SOLR_LOGGING_WATCHER_THRESHOLD, null), - getInt(CfgProp.SOLR_LOGGING_WATCHER_SIZE, 50) + get(CfgProp.SOLR_LOGGING_ENABLED, true), + loggingClass, + loggingWatcherThreshold, + get(CfgProp.SOLR_LOGGING_WATCHER_SIZE, 50) ); } public int getTransientCacheSize() { - return getInt(CfgProp.SOLR_TRANSIENTCACHESIZE, Integer.MAX_VALUE); + return get(CfgProp.SOLR_TRANSIENTCACHESIZE, Integer.MAX_VALUE); } public ConfigSetService createCoreConfigService(SolrResourceLoader loader, ZkController zkController) { @@ -282,11 +277,11 @@ public abstract class ConfigSolr { } protected Config config; - protected Map propMap = new HashMap<>(); + protected Map propMap = new HashMap<>(); public ConfigSolr(Config config) { this.config = config; - + config.substituteProperties(); } // for extension & testing. @@ -298,22 +293,12 @@ public abstract class ConfigSolr { return config; } - public int getInt(CfgProp prop, int def) { - String val = propMap.get(prop); - if (val != null) val = PropertiesUtil.substituteProperty(val, null); - return (val == null) ? def : Integer.parseInt(val); - } - - public boolean getBool(CfgProp prop, boolean defValue) { - String val = propMap.get(prop); - if (val != null) val = PropertiesUtil.substituteProperty(val, null); - return (val == null) ? defValue : Boolean.parseBoolean(val); - } - - public String get(CfgProp prop, String def) { - String val = propMap.get(prop); - if (val != null) val = PropertiesUtil.substituteProperty(val, null); - return (val == null) ? def : val; + @SuppressWarnings("unchecked") + public T get(CfgProp key, T defaultValue) { + if (propMap.containsKey(key) && propMap.get(key) != null) { + return (T) propMap.get(key); + } + return defaultValue; } public Properties getSolrProperties(String path) { diff --git a/solr/core/src/java/org/apache/solr/core/ConfigSolrXml.java b/solr/core/src/java/org/apache/solr/core/ConfigSolrXml.java index 7e946b12623..1b51b42073c 100644 --- a/solr/core/src/java/org/apache/solr/core/ConfigSolrXml.java +++ b/solr/core/src/java/org/apache/solr/core/ConfigSolrXml.java @@ -18,11 +18,23 @@ package org.apache.solr.core; */ import org.apache.solr.common.SolrException; -import org.apache.solr.util.PropertiesUtil; +import org.apache.solr.common.util.NamedList; +import org.apache.solr.util.DOMUtil; + +import com.google.common.base.Function; +import com.google.common.base.Functions; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.Locale; /** @@ -39,27 +51,25 @@ public class ConfigSolrXml extends ConfigSolr { try { checkForIllegalConfig(); fillPropMap(); - config.substituteProperties(); coresLocator = new CorePropertiesLocator(getCoreRootDirectory()); - } - catch (IOException e) { + } catch (IOException e) { throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e); } } - + private void checkForIllegalConfig() throws IOException { - + // Do sanity checks - we don't want to find old style config failIfFound("solr/@coreLoadThreads"); failIfFound("solr/@persistent"); failIfFound("solr/@sharedLib"); failIfFound("solr/@zkHost"); - + failIfFound("solr/logging/@class"); failIfFound("solr/logging/@enabled"); failIfFound("solr/logging/watcher/@size"); failIfFound("solr/logging/watcher/@threshold"); - + failIfFound("solr/cores/@adminHandler"); failIfFound("solr/cores/@distribUpdateConnTimeout"); failIfFound("solr/cores/@distribUpdateSoTimeout"); @@ -73,7 +83,7 @@ public class ConfigSolrXml extends ConfigSolr { failIfFound("solr/cores/@shareSchema"); failIfFound("solr/cores/@transientCacheSize"); failIfFound("solr/cores/@zkClientTimeout"); - + // These have no counterpart in 5.0, asking for any of these in Solr 5.0 // will result in an error being // thrown. @@ -82,7 +92,7 @@ public class ConfigSolrXml extends ConfigSolr { failIfFound("solr/cores/@adminPath"); } - + private void failIfFound(String xPath) { if (config.getVal(xPath, false) != null) { @@ -91,43 +101,144 @@ public class ConfigSolrXml extends ConfigSolr { } } - // We can do this in 5.0 when we read the solr.xml since we don't need to keep the original around for persistence. - private String doSub(String path) { - String val = config.getVal(path, false); - if (val != null) { - val = PropertiesUtil.substituteProperty(val, null); + private NamedList readNodeListAsNamedList(String path) { + NodeList nodes = config.getNodeList(path, false); + if (nodes != null) { + NamedList namedList = DOMUtil.nodesToNamedList(nodes); + return namedList; } - return val; + return new NamedList<>(); } - - private void fillPropMap() { - propMap.put(CfgProp.SOLR_ADMINHANDLER, doSub("solr/str[@name='adminHandler']")); - propMap.put(CfgProp.SOLR_COLLECTIONSHANDLER, doSub("solr/str[@name='collectionsHandler']")); - propMap.put(CfgProp.SOLR_INFOHANDLER, doSub("solr/str[@name='infoHandler']")); - propMap.put(CfgProp.SOLR_CORELOADTHREADS, doSub("solr/int[@name='coreLoadThreads']")); - propMap.put(CfgProp.SOLR_COREROOTDIRECTORY, doSub("solr/str[@name='coreRootDirectory']")); - propMap.put(CfgProp.SOLR_DISTRIBUPDATECONNTIMEOUT, doSub("solr/solrcloud/int[@name='distribUpdateConnTimeout']")); - propMap.put(CfgProp.SOLR_DISTRIBUPDATESOTIMEOUT, doSub("solr/solrcloud/int[@name='distribUpdateSoTimeout']")); - propMap.put(CfgProp.SOLR_MAXUPDATECONNECTIONS, doSub("solr/solrcloud/int[@name='maxUpdateConnections']")); - propMap.put(CfgProp.SOLR_MAXUPDATECONNECTIONSPERHOST, doSub("solr/solrcloud/int[@name='maxUpdateConnectionsPerHost']")); - propMap.put(CfgProp.SOLR_HOST, doSub("solr/solrcloud/str[@name='host']")); - propMap.put(CfgProp.SOLR_HOSTCONTEXT, doSub("solr/solrcloud/str[@name='hostContext']")); - propMap.put(CfgProp.SOLR_HOSTPORT, doSub("solr/solrcloud/int[@name='hostPort']")); - propMap.put(CfgProp.SOLR_LEADERVOTEWAIT, doSub("solr/solrcloud/int[@name='leaderVoteWait']")); - propMap.put(CfgProp.SOLR_LEADERCONFLICTRESOLVEWAIT, doSub("solr/solrcloud/int[@name='leaderConflictResolveWait']")); - propMap.put(CfgProp.SOLR_GENERICCORENODENAMES, doSub("solr/solrcloud/bool[@name='genericCoreNodeNames']")); - propMap.put(CfgProp.SOLR_MANAGEMENTPATH, doSub("solr/str[@name='managementPath']")); - propMap.put(CfgProp.SOLR_SHAREDLIB, doSub("solr/str[@name='sharedLib']")); - propMap.put(CfgProp.SOLR_SHARESCHEMA, doSub("solr/str[@name='shareSchema']")); - propMap.put(CfgProp.SOLR_TRANSIENTCACHESIZE, doSub("solr/int[@name='transientCacheSize']")); - propMap.put(CfgProp.SOLR_ZKCLIENTTIMEOUT, doSub("solr/solrcloud/int[@name='zkClientTimeout']")); - propMap.put(CfgProp.SOLR_ZKHOST, doSub("solr/solrcloud/str[@name='zkHost']")); - propMap.put(CfgProp.SOLR_CONFIGSETBASEDIR, doSub("solr/str[@name='configSetBaseDir']")); - propMap.put(CfgProp.SOLR_LOGGING_CLASS, doSub("solr/logging/str[@name='class']")); - propMap.put(CfgProp.SOLR_LOGGING_ENABLED, doSub("solr/logging/str[@name='enabled']")); - propMap.put(CfgProp.SOLR_LOGGING_WATCHER_SIZE, doSub("solr/logging/watcher/int[@name='size']")); - propMap.put(CfgProp.SOLR_LOGGING_WATCHER_THRESHOLD, doSub("solr/logging/watcher/int[@name='threshold']")); + private void fillPropMap() { + NamedList unknownConfigParams = new NamedList<>(); + + // shardHandlerFactory is parsed differently in the base class as a plugin, so we're excluding this node from the node list + fillSolrSection(readNodeListAsNamedList("solr/*[@name][not(name()='shardHandlerFactory')]")); + + thereCanBeOnlyOne("solr/solrcloud",""); + fillSolrCloudSection(readNodeListAsNamedList("solr/solrcloud/*[@name]")); + + thereCanBeOnlyOne("solr/logging",""); + thereCanBeOnlyOne("solr/logging/watcher","Logging "); + fillLoggingSection(readNodeListAsNamedList("solr/logging/*[@name]"), + readNodeListAsNamedList("solr/logging/watcher/*[@name]")); + } + + private void fillSolrSection(NamedList nl) { + String s = "Main"; + storeConfigPropertyAsString(s, nl, CfgProp.SOLR_ADMINHANDLER, "adminHandler"); + storeConfigPropertyAsString(s, nl, CfgProp.SOLR_COLLECTIONSHANDLER, "collectionsHandler"); + storeConfigPropertyAsString(s, nl, CfgProp.SOLR_INFOHANDLER, "infoHandler"); + storeConfigPropertyAsString(s, nl, CfgProp.SOLR_COREROOTDIRECTORY, "coreRootDirectory"); + storeConfigPropertyAsString(s, nl, CfgProp.SOLR_MANAGEMENTPATH, "managementPath"); + storeConfigPropertyAsString(s, nl, CfgProp.SOLR_SHAREDLIB, "sharedLib"); + storeConfigPropertyAsString(s, nl, CfgProp.SOLR_CONFIGSETBASEDIR, "configSetBaseDir"); + + storeConfigPropertyAsBoolean(s, nl, CfgProp.SOLR_SHARESCHEMA, "shareSchema"); + + storeConfigPropertyAsInt(s, nl, CfgProp.SOLR_CORELOADTHREADS, "coreLoadThreads"); + storeConfigPropertyAsInt(s, nl, CfgProp.SOLR_TRANSIENTCACHESIZE, "transientCacheSize"); + + errorOnLeftOvers(s, nl); + } + + private void fillSolrCloudSection(NamedList nl) { + + String s = ""; + storeConfigPropertyAsInt(s, nl, CfgProp.SOLR_DISTRIBUPDATECONNTIMEOUT, "distribUpdateConnTimeout"); + storeConfigPropertyAsInt(s, nl, CfgProp.SOLR_DISTRIBUPDATESOTIMEOUT, "distribUpdateSoTimeout"); + storeConfigPropertyAsInt(s, nl, CfgProp.SOLR_MAXUPDATECONNECTIONS, "maxUpdateConnections"); + storeConfigPropertyAsInt(s, nl, CfgProp.SOLR_MAXUPDATECONNECTIONSPERHOST, "maxUpdateConnectionsPerHost"); + storeConfigPropertyAsInt(s, nl, CfgProp.SOLR_LEADERVOTEWAIT, "leaderVoteWait"); + storeConfigPropertyAsInt(s, nl, CfgProp.SOLR_LEADERCONFLICTRESOLVEWAIT, "leaderConflictResolveWait"); + storeConfigPropertyAsInt(s, nl, CfgProp.SOLR_ZKCLIENTTIMEOUT, "zkClientTimeout"); + + storeConfigPropertyAsString(s, nl, CfgProp.SOLR_HOST, "host"); + storeConfigPropertyAsString(s, nl, CfgProp.SOLR_HOSTCONTEXT, "hostContext"); + storeConfigPropertyAsString(s, nl, CfgProp.SOLR_HOSTPORT, "hostPort"); + storeConfigPropertyAsString(s, nl, CfgProp.SOLR_ZKHOST, "zkHost"); + + storeConfigPropertyAsBoolean(s, nl, CfgProp.SOLR_GENERICCORENODENAMES, "genericCoreNodeNames"); + + errorOnLeftOvers(s, nl); + } + + private void fillLoggingSection(NamedList loggingConfig, + NamedList loggingWatcherConfig) { + + String s = ""; + storeConfigPropertyAsString(s, loggingConfig, CfgProp.SOLR_LOGGING_CLASS, "class"); + storeConfigPropertyAsBoolean(s, loggingConfig, CfgProp.SOLR_LOGGING_ENABLED, "enabled"); + + errorOnLeftOvers(s, loggingConfig); + + s = "Logging "; + storeConfigPropertyAsInt(s, loggingWatcherConfig, CfgProp.SOLR_LOGGING_WATCHER_SIZE, "size"); + storeConfigPropertyAsString(s, loggingWatcherConfig, CfgProp.SOLR_LOGGING_WATCHER_THRESHOLD, "threshold"); + + errorOnLeftOvers(s, loggingWatcherConfig); + } + + + private void storeConfigProperty(String section, NamedList config, CfgProp propertyKey, String name, Function valueTransformer, Class clazz) { + List values = config.removeAll(name); + if (null != values && 0 != values.size()) { + if (1 < values.size()) { + throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, + String.format(Locale.ROOT, + "%s section of solr.xml contains duplicated '%s'"+ + " in solr.xml: %s", section, name, values)); + } else { + Object value = values.get(0); + if (value != null) { + if (value.getClass().isAssignableFrom(clazz)) { + propMap.put(propertyKey, value); + } else { + propMap.put(propertyKey, valueTransformer.apply(value)); + } + } else { + propMap.put(propertyKey, null); + } + } + } + } + + private void storeConfigPropertyAsString(String section, NamedList config, CfgProp propertyKey, String name) { + storeConfigProperty(section, config, propertyKey, name, Functions.toStringFunction(), String.class); + } + + private void storeConfigPropertyAsInt(String section, NamedList config, CfgProp propertyKey, String xmlElementName) { + storeConfigProperty(section, config, propertyKey, xmlElementName, TO_INT_FUNCTION, Integer.class); + } + + private void storeConfigPropertyAsBoolean(String section, NamedList config, CfgProp propertyKey, String name) { + storeConfigProperty(section, config, propertyKey, name, TO_BOOLEAN_FUNCTION, Boolean.class); + } + + /** throws an error if more then one element matching the xpath */ + private void thereCanBeOnlyOne(String xpath, String section) { + NodeList lst = config.getNodeList(xpath, false); + if (1 < lst.getLength()) + throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, + lst.getLength() + " instances of " + section + " found in solr.xml"); + } + + /** logs each item in leftovers and then throws an exception with a summary */ + private void errorOnLeftOvers(String section, NamedList leftovers) { + + if (null == leftovers || 0 == leftovers.size()) return; + + List unknownElements = new ArrayList(leftovers.size()); + for (Map.Entry unknownElement : leftovers) { + log.error("Unknown config parameter in {} section of solr.xml: {} -> {}", + section, unknownElement.getKey(), unknownElement.getValue()); + unknownElements.add(unknownElement.getKey()); + } + if (! unknownElements.isEmpty() ) { + throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, + String.format(Locale.ROOT, "%s section of solr.xml contains %d unknown config parameter(s): %s", section, unknownElements.size(), unknownElements)); + } } @Override @@ -155,5 +266,34 @@ public class ConfigSolrXml extends ConfigSolr { return coresLocator; } + private static final Function, String> GET_KEY_FUNCTION = new Function, String>() { + @Override + public String apply(Map.Entry input) { + return input.getKey(); + } + }; + + private static final Function TO_INT_FUNCTION = new Function() { + @Override + public Integer apply(Object input) { + try { + return Integer.parseInt(String.valueOf(input)); + } catch (NumberFormatException exc) { + throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, + String.format(Locale.ROOT, + "Value of '%s' can not be parsed as 'int'", input)); + } + } + }; + + private static final Function TO_BOOLEAN_FUNCTION = new Function() { + @Override + public Boolean apply(Object input) { + if (input instanceof String) { + return Boolean.valueOf(String.valueOf(input)); + } + throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, String.format(Locale.ROOT, "Value of '%s' can not be parsed as 'bool'", input)); + } + }; } diff --git a/solr/core/src/java/org/apache/solr/core/ConfigSolrXmlOld.java b/solr/core/src/java/org/apache/solr/core/ConfigSolrXmlOld.java index 00d02eff1fb..a7754a81e85 100644 --- a/solr/core/src/java/org/apache/solr/core/ConfigSolrXmlOld.java +++ b/solr/core/src/java/org/apache/solr/core/ConfigSolrXmlOld.java @@ -17,14 +17,6 @@ package org.apache.solr.core; * limitations under the License. */ -import org.apache.solr.common.SolrException; -import org.apache.solr.util.DOMUtil; -import org.apache.solr.util.PropertiesUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import java.io.File; @@ -38,6 +30,15 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import org.apache.commons.lang.StringUtils; +import org.apache.solr.common.SolrException; +import org.apache.solr.util.DOMUtil; +import org.apache.solr.util.PropertiesUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + /** * @@ -62,7 +63,6 @@ public class ConfigSolrXmlOld extends ConfigSolr { try { checkForIllegalConfig(); fillPropMap(); - config.substituteProperties(); initCoreList(); this.persistor = isPersistent() ? new SolrXMLCoresLocator(originalXML, this) : new SolrXMLCoresLocator.NonPersistingLocator(originalXML, this); @@ -123,63 +123,59 @@ public class ConfigSolrXmlOld extends ConfigSolr { } private void fillPropMap() { - - propMap.put(CfgProp.SOLR_CORELOADTHREADS, - config.getVal("solr/@coreLoadThreads", false)); - propMap - .put(CfgProp.SOLR_SHAREDLIB, config.getVal("solr/@sharedLib", false)); - propMap.put(CfgProp.SOLR_ZKHOST, config.getVal("solr/@zkHost", false)); - - propMap.put(CfgProp.SOLR_LOGGING_CLASS, - config.getVal("solr/logging/@class", false)); - propMap.put(CfgProp.SOLR_LOGGING_ENABLED, - config.getVal("solr/logging/@enabled", false)); - propMap.put(CfgProp.SOLR_LOGGING_WATCHER_SIZE, - config.getVal("solr/logging/watcher/@size", false)); - propMap.put(CfgProp.SOLR_LOGGING_WATCHER_THRESHOLD, - config.getVal("solr/logging/watcher/@threshold", false)); - - propMap.put(CfgProp.SOLR_ADMINHANDLER, - config.getVal("solr/cores/@adminHandler", false)); - propMap.put(CfgProp.SOLR_COLLECTIONSHANDLER, config.getVal("solr/cores/@collectionsHandler", false)); - propMap.put(CfgProp.SOLR_INFOHANDLER, config.getVal("solr/cores/@infoHandler", false)); - propMap.put(CfgProp.SOLR_DISTRIBUPDATECONNTIMEOUT, - config.getVal("solr/cores/@distribUpdateConnTimeout", false)); - propMap.put(CfgProp.SOLR_DISTRIBUPDATESOTIMEOUT, - config.getVal("solr/cores/@distribUpdateSoTimeout", false)); - propMap.put(CfgProp.SOLR_MAXUPDATECONNECTIONS, - config.getVal("solr/cores/@maxUpdateConnections", false)); - propMap.put(CfgProp.SOLR_MAXUPDATECONNECTIONSPERHOST, - config.getVal("solr/cores/@maxUpdateConnectionsPerHost", false)); - propMap.put(CfgProp.SOLR_HOST, config.getVal("solr/cores/@host", false)); - propMap.put(CfgProp.SOLR_HOSTCONTEXT, - config.getVal("solr/cores/@hostContext", false)); - propMap.put(CfgProp.SOLR_HOSTPORT, - config.getVal("solr/cores/@hostPort", false)); - propMap.put(CfgProp.SOLR_LEADERVOTEWAIT, - config.getVal("solr/cores/@leaderVoteWait", false)); - propMap.put(CfgProp.SOLR_GENERICCORENODENAMES, - config.getVal("solr/cores/@genericCoreNodeNames", false)); - propMap.put(CfgProp.SOLR_MANAGEMENTPATH, - config.getVal("solr/cores/@managementPath", false)); - propMap.put(CfgProp.SOLR_SHARESCHEMA, - config.getVal("solr/cores/@shareSchema", false)); - propMap.put(CfgProp.SOLR_TRANSIENTCACHESIZE, - config.getVal("solr/cores/@transientCacheSize", false)); - propMap.put(CfgProp.SOLR_ZKCLIENTTIMEOUT, - config.getVal("solr/cores/@zkClientTimeout", false)); - propMap.put(CfgProp.SOLR_CONFIGSETBASEDIR, config.getVal("solr/cores/@configSetBaseDir", false)); + storeConfigPropertyAsInt(CfgProp.SOLR_CORELOADTHREADS, "solr/@coreLoadThreads"); + storeConfigPropertyAsString(CfgProp.SOLR_SHAREDLIB, "solr/@sharedLib"); + storeConfigPropertyAsString(CfgProp.SOLR_ZKHOST, "solr/@zkHost"); + storeConfigPropertyAsString(CfgProp.SOLR_LOGGING_CLASS, "solr/logging/@class"); + storeConfigPropertyAsBoolean(CfgProp.SOLR_LOGGING_ENABLED, "solr/logging/@enabled"); + storeConfigPropertyAsInt(CfgProp.SOLR_LOGGING_WATCHER_SIZE, "solr/logging/watcher/@size"); + storeConfigPropertyAsString(CfgProp.SOLR_LOGGING_WATCHER_THRESHOLD, "solr/logging/watcher/@threshold"); + storeConfigPropertyAsString(CfgProp.SOLR_ADMINHANDLER, "solr/cores/@adminHandler"); + storeConfigPropertyAsString(CfgProp.SOLR_COLLECTIONSHANDLER, "solr/cores/@collectionsHandler"); + storeConfigPropertyAsString(CfgProp.SOLR_INFOHANDLER, "solr/cores/@infoHandler"); + storeConfigPropertyAsInt(CfgProp.SOLR_DISTRIBUPDATECONNTIMEOUT, "solr/cores/@distribUpdateConnTimeout"); + storeConfigPropertyAsInt(CfgProp.SOLR_DISTRIBUPDATESOTIMEOUT, "solr/cores/@distribUpdateSoTimeout"); + storeConfigPropertyAsInt(CfgProp.SOLR_MAXUPDATECONNECTIONS, "solr/cores/@maxUpdateConnections"); + storeConfigPropertyAsInt(CfgProp.SOLR_MAXUPDATECONNECTIONSPERHOST, "solr/cores/@maxUpdateConnectionsPerHost"); + storeConfigPropertyAsString(CfgProp.SOLR_HOST, "solr/cores/@host"); + storeConfigPropertyAsString(CfgProp.SOLR_HOSTCONTEXT, "solr/cores/@hostContext"); + storeConfigPropertyAsString(CfgProp.SOLR_HOSTPORT, "solr/cores/@hostPort"); + storeConfigPropertyAsInt(CfgProp.SOLR_LEADERVOTEWAIT, "solr/cores/@leaderVoteWait"); + storeConfigPropertyAsBoolean(CfgProp.SOLR_GENERICCORENODENAMES, "solr/cores/@genericCoreNodeNames"); + storeConfigPropertyAsString(CfgProp.SOLR_MANAGEMENTPATH, "solr/cores/@managementPath"); + storeConfigPropertyAsBoolean(CfgProp.SOLR_SHARESCHEMA, "solr/cores/@shareSchema"); + storeConfigPropertyAsInt(CfgProp.SOLR_TRANSIENTCACHESIZE, "solr/cores/@transientCacheSize"); + storeConfigPropertyAsInt(CfgProp.SOLR_ZKCLIENTTIMEOUT, "solr/cores/@zkClientTimeout"); + storeConfigPropertyAsString(CfgProp.SOLR_CONFIGSETBASEDIR, "solr/cores/@configSetBaseDir"); // These have no counterpart in 5.0, asking, for any of these in Solr 5.0 // will result in an error being // thrown. - propMap.put(CfgProp.SOLR_CORES_DEFAULT_CORE_NAME, - config.getVal("solr/cores/@defaultCoreName", false)); - propMap.put(CfgProp.SOLR_PERSISTENT, - config.getVal("solr/@persistent", false)); - propMap.put(CfgProp.SOLR_ADMINPATH, - config.getVal("solr/cores/@adminPath", false)); - + storeConfigPropertyAsString(CfgProp.SOLR_CORES_DEFAULT_CORE_NAME, "solr/cores/@defaultCoreName"); + storeConfigPropertyAsString(CfgProp.SOLR_PERSISTENT, "solr/@persistent"); + storeConfigPropertyAsString(CfgProp.SOLR_ADMINPATH, "solr/cores/@adminPath"); + } + + private void storeConfigPropertyAsInt(CfgProp key, String xmlPath) { + String valueAsString = config.getVal(xmlPath, false); + if (StringUtils.isNotBlank(valueAsString)) { + propMap.put(key, Integer.parseInt(valueAsString)); + } else { + propMap.put(key, null); + } + } + + private void storeConfigPropertyAsBoolean(CfgProp key, String xmlPath) { + String valueAsString = config.getVal(xmlPath, false); + if (StringUtils.isNotBlank(valueAsString)) { + propMap.put(key, Boolean.parseBoolean(valueAsString)); + } else { + propMap.put(key, null); + } + } + + private void storeConfigPropertyAsString(CfgProp key, String xmlPath) { + propMap.put(key, config.getVal(xmlPath, false)); } private void initCoreList() throws IOException { @@ -196,7 +192,6 @@ public class ConfigSolrXmlOld extends ConfigSolr { String name = DOMUtil.getAttr(node, CoreDescriptor.CORE_NAME, null); String dataDir = DOMUtil.getAttr(node, CoreDescriptor.CORE_DATADIR, null); - if (dataDir != null) dataDir = PropertiesUtil.substituteProperty(dataDir, null); if (name != null) { if (!names.contains(name)) { names.add(name); @@ -208,7 +203,6 @@ public class ConfigSolrXmlOld extends ConfigSolr { } String instDir = DOMUtil.getAttr(node, CoreDescriptor.CORE_INSTDIR, null); - if (instDir != null) instDir = PropertiesUtil.substituteProperty(instDir, null); if (dataDir != null) { String absData = null; @@ -259,7 +253,7 @@ public class ConfigSolrXmlOld extends ConfigSolr { String propVal = DOMUtil.getAttr(node, property); if (propVal == null) propVal = defaultVal; - return PropertiesUtil.substituteProperty(propVal, null); + return propVal; } } } diff --git a/solr/core/src/test-files/solr/solr-50-all.xml b/solr/core/src/test-files/solr/solr-50-all.xml index 32e52fb0028..7d71f58849e 100644 --- a/solr/core/src/test-files/solr/solr-50-all.xml +++ b/solr/core/src/test-files/solr/solr-50-all.xml @@ -23,7 +23,7 @@ testInfoHandler testManagementPath testSharedLib - ${shareSchema:testShareSchema} + ${shareSchema:true} 66 @@ -41,7 +41,7 @@ testLoggingClass - testLoggingEnabled + true 88 99 diff --git a/solr/core/src/test/org/apache/solr/cloud/SolrXmlInZkTest.java b/solr/core/src/test/org/apache/solr/cloud/SolrXmlInZkTest.java index de2dca7fef6..42708796754 100644 --- a/solr/core/src/test/org/apache/solr/cloud/SolrXmlInZkTest.java +++ b/solr/core/src/test/org/apache/solr/cloud/SolrXmlInZkTest.java @@ -158,7 +158,7 @@ public class SolrXmlInZkTest extends SolrTestCaseJ4 { fail("Should have thrown an exception here"); } catch (InvocationTargetException ite) { assertTrue("Should be failing to create default solr.xml in code", - ite.getTargetException().getCause().getMessage().indexOf("solr.xml does not exist") != -1); + ite.getCause().getMessage().contains("solr.xml does not exist")); } finally { closeZK(); } diff --git a/solr/core/src/test/org/apache/solr/core/TestSolrXml.java b/solr/core/src/test/org/apache/solr/core/TestSolrXml.java index faa5b124a27..590c98bc47e 100644 --- a/solr/core/src/test/org/apache/solr/core/TestSolrXml.java +++ b/solr/core/src/test/org/apache/solr/core/TestSolrXml.java @@ -19,65 +19,77 @@ package org.apache.solr.core; import java.io.File; import java.io.IOException; - -import org.apache.commons.io.FileUtils; -import org.apache.solr.SolrTestCaseJ4; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.RuleChain; -import org.junit.rules.TestRule; +import java.util.Random; +import java.util.Locale; import com.carrotsearch.randomizedtesting.rules.SystemPropertiesRestoreRule; +import com.google.common.base.Charsets; +import org.apache.commons.io.FileUtils; +import org.apache.lucene.util.TestUtil; +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.common.SolrException; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.rules.ExpectedException; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; public class TestSolrXml extends SolrTestCaseJ4 { @Rule public TestRule solrTestRules = RuleChain.outerRule(new SystemPropertiesRestoreRule()); + @Rule + public ExpectedException expectedException = ExpectedException.none(); - private final File solrHome = createTempDir(); + // tmp dir, cleanedup automaticly. + private static File solrHome = null; + private static SolrResourceLoader loader = null; + + @BeforeClass + public static void setupLoader() throws Exception { + solrHome = createTempDir(); + loader = new SolrResourceLoader(solrHome.getAbsolutePath()); + } + + @AfterClass + public static void cleanupLoader() throws Exception { + solrHome = null; + loader = null; + } - @Test public void testAllInfoPresent() throws IOException { File testSrcRoot = new File(SolrTestCaseJ4.TEST_HOME()); FileUtils.copyFile(new File(testSrcRoot, "solr-50-all.xml"), new File(solrHome, "solr.xml")); - SolrResourceLoader loader = null; - try { - loader = new SolrResourceLoader(solrHome.getAbsolutePath()); - ConfigSolr cfg = ConfigSolr.fromSolrHome(loader, solrHome.getAbsolutePath()); - - assertEquals("Did not find expected value", "testAdminHandler", cfg.get(ConfigSolr.CfgProp.SOLR_ADMINHANDLER, null)); - assertEquals("Did not find expected value", "testCollectionsHandler", cfg.get(ConfigSolr.CfgProp.SOLR_COLLECTIONSHANDLER, null)); - assertEquals("Did not find expected value", "testInfoHandler", cfg.get(ConfigSolr.CfgProp.SOLR_INFOHANDLER, null)); - assertEquals("Did not find expected value", 11, cfg.getInt(ConfigSolr.CfgProp.SOLR_CORELOADTHREADS, 0)); - assertEquals("Did not find expected value", "testCoreRootDirectory", cfg.get(ConfigSolr.CfgProp.SOLR_COREROOTDIRECTORY, null)); - assertEquals("Did not find expected value", 22, cfg.getInt(ConfigSolr.CfgProp.SOLR_DISTRIBUPDATECONNTIMEOUT, 0)); - assertEquals("Did not find expected value", 33, cfg.getInt(ConfigSolr.CfgProp.SOLR_DISTRIBUPDATESOTIMEOUT, 0)); - assertEquals("Did not find expected value", 3, cfg.getInt(ConfigSolr.CfgProp.SOLR_MAXUPDATECONNECTIONS, 0)); - assertEquals("Did not find expected value", 37, cfg.getInt(ConfigSolr.CfgProp.SOLR_MAXUPDATECONNECTIONSPERHOST, 0)); - assertEquals("Did not find expected value", "testHost", cfg.get(ConfigSolr.CfgProp.SOLR_HOST, null)); - assertEquals("Did not find expected value", "testHostContext", cfg.get(ConfigSolr.CfgProp.SOLR_HOSTCONTEXT, null)); - assertEquals("Did not find expected value", 44, cfg.getInt(ConfigSolr.CfgProp.SOLR_HOSTPORT, 0)); - assertEquals("Did not find expected value", 55, cfg.getInt(ConfigSolr.CfgProp.SOLR_LEADERVOTEWAIT, 0)); - assertEquals("Did not find expected value", "testLoggingClass", cfg.get(ConfigSolr.CfgProp.SOLR_LOGGING_CLASS, null)); - assertEquals("Did not find expected value", "testLoggingEnabled", cfg.get(ConfigSolr.CfgProp.SOLR_LOGGING_ENABLED, null)); - assertEquals("Did not find expected value", 88, cfg.getInt(ConfigSolr.CfgProp.SOLR_LOGGING_WATCHER_SIZE, 0)); - assertEquals("Did not find expected value", 99, cfg.getInt(ConfigSolr.CfgProp.SOLR_LOGGING_WATCHER_THRESHOLD, 0)); - assertEquals("Did not find expected value", "testManagementPath", cfg.get(ConfigSolr.CfgProp.SOLR_MANAGEMENTPATH, null)); - assertEquals("Did not find expected value", "testSharedLib", cfg.get(ConfigSolr.CfgProp.SOLR_SHAREDLIB, null)); - assertEquals("Did not find expected value", "testShareSchema", cfg.get(ConfigSolr.CfgProp.SOLR_SHARESCHEMA, null)); - assertEquals("Did not find expected value", 66, cfg.getInt(ConfigSolr.CfgProp.SOLR_TRANSIENTCACHESIZE, 0)); - assertEquals("Did not find expected value", 77, cfg.getInt(ConfigSolr.CfgProp.SOLR_ZKCLIENTTIMEOUT, 0)); - assertEquals("Did not find expected value", "testZkHost", cfg.get(ConfigSolr.CfgProp.SOLR_ZKHOST, null)); - assertNull("Did not find expected value", cfg.get(ConfigSolr.CfgProp.SOLR_PERSISTENT, null)); - assertNull("Did not find expected value", cfg.get(ConfigSolr.CfgProp.SOLR_CORES_DEFAULT_CORE_NAME, null)); - assertNull("Did not find expected value", cfg.get(ConfigSolr.CfgProp.SOLR_ADMINPATH, null)); - } - finally { - loader.close(); - } - + ConfigSolr cfg = ConfigSolr.fromSolrHome(loader, solrHome.getAbsolutePath()); + + assertEquals("core admin handler class", "testAdminHandler", cfg.getCoreAdminHandlerClass()); + assertEquals("collection handler class", "testCollectionsHandler", cfg.getCollectionsHandlerClass()); + assertEquals("info handler class", "testInfoHandler", cfg.getInfoHandlerClass()); + assertEquals("core load threads", 11, cfg.getCoreLoadThreadCount()); + assertEquals("core root dir", "testCoreRootDirectory", cfg.getCoreRootDirectory()); + assertEquals("distrib conn timeout", 22, cfg.getDistributedConnectionTimeout()); + assertEquals("distrib socket timeout", 33, cfg.getDistributedSocketTimeout()); + assertEquals("max update conn", 3, cfg.getMaxUpdateConnections()); + assertEquals("max update conn/host", 37, cfg.getMaxUpdateConnectionsPerHost()); + assertEquals("host", "testHost", cfg.getHost()); + assertEquals("zk host context", "testHostContext", cfg.getZkHostContext()); + assertEquals("zk host port", "44", cfg.getZkHostPort()); + assertEquals("leader vote wait", 55, cfg.getLeaderVoteWait()); + assertEquals("logging class", "testLoggingClass", cfg.getLogWatcherConfig().getLoggingClass()); + assertEquals("log watcher", true, cfg.getLogWatcherConfig().isEnabled()); + assertEquals("log watcher size", 88, cfg.getLogWatcherConfig().getWatcherSize()); + assertEquals("log watcher thresh", "99", cfg.getLogWatcherConfig().getWatcherThreshold()); + assertEquals("manage path", "testManagementPath", cfg.getManagementPath()); + assertEquals("shardLib", "testSharedLib", cfg.getSharedLibDirectory()); + assertEquals("schema cache", true, cfg.hasSchemaCache()); + assertEquals("trans cache size", 66, cfg.getTransientCacheSize()); + assertEquals("zk client timeout", 77, cfg.getZkClientTimeout()); + assertEquals("zk host", "testZkHost", cfg.getZkHost()); + assertEquals("persistent", true, cfg.isPersistent()); + assertEquals("core admin path", ConfigSolr.DEFAULT_CORE_ADMIN_PATH, cfg.getAdminPath()); } // Test a few property substitutions that happen to be in solr-50-all.xml. @@ -85,24 +97,213 @@ public class TestSolrXml extends SolrTestCaseJ4 { System.setProperty("coreRootDirectory", "myCoreRoot"); System.setProperty("hostPort", "8888"); - System.setProperty("shareSchema", "newShareSchema"); + System.setProperty("shareSchema", "false"); System.setProperty("socketTimeout", "220"); System.setProperty("connTimeout", "200"); File testSrcRoot = new File(SolrTestCaseJ4.TEST_HOME()); FileUtils.copyFile(new File(testSrcRoot, "solr-50-all.xml"), new File(solrHome, "solr.xml")); - SolrResourceLoader loader = null; - try { - loader = new SolrResourceLoader(solrHome.getAbsolutePath()); - ConfigSolr cfg = ConfigSolr.fromSolrHome(loader, solrHome.getAbsolutePath()); - assertEquals("Did not find expected value", "myCoreRoot", cfg.get(ConfigSolr.CfgProp.SOLR_COREROOTDIRECTORY, null)); - assertEquals("Did not find expected value", 8888, cfg.getInt(ConfigSolr.CfgProp.SOLR_HOSTPORT, 0)); - assertEquals("Did not find expected value", "newShareSchema", cfg.get(ConfigSolr.CfgProp.SOLR_SHARESCHEMA, null)); - } - finally { - loader.close(); - } + ConfigSolr cfg = ConfigSolr.fromSolrHome(loader, solrHome.getAbsolutePath()); + assertEquals("core root dir", "myCoreRoot", cfg.getCoreRootDirectory()); + assertEquals("zk host port", "8888", cfg.getZkHostPort()); + assertEquals("schema cache", false, cfg.hasSchemaCache()); + } + + public void testExplicitNullGivesDefaults() throws IOException { + // 2 diff options, one where the default is in fact null, and one where it isn't + String solrXml = ""; + + ConfigSolr cfg = ConfigSolr.fromString(loader, solrXml); + assertEquals("host", null, cfg.getHost()); + assertEquals("leaderVoteWait", 180000, cfg.getLeaderVoteWait()); + } + + public void testIntAsLongBad() throws IOException { + String bad = ""+TestUtil.nextLong(random(), Integer.MAX_VALUE, Long.MAX_VALUE); + String solrXml = ""+bad+""; + + expectedException.expect(SolrException.class); + expectedException.expectMessage(String.format(Locale.ROOT, "Value of '%s' can not be parsed as 'int'", bad)); + ConfigSolr cfg = ConfigSolr.fromString(loader, solrXml); + } + + public void testIntAsLongOk() throws IOException { + int ok = random().nextInt(); + String solrXml = ""+ok+""; + ConfigSolr cfg = ConfigSolr.fromString(loader, solrXml); + assertEquals(ok, cfg.getTransientCacheSize()); + } + + public void testMultiCloudSectionError() throws IOException { + String solrXml = "" + + "true" + + "false" + + ""; + expectedException.expect(SolrException.class); + expectedException.expectMessage("2 instances of found in solr.xml"); + ConfigSolr cfg = ConfigSolr.fromString(loader, solrXml); + } + + public void testMultiLoggingSectionError() throws IOException { + String solrXml = "" + + "foo" + + "foo" + + ""; + expectedException.expect(SolrException.class); + expectedException.expectMessage("2 instances of found in solr.xml"); + ConfigSolr cfg = ConfigSolr.fromString(loader, solrXml); + } + + public void testMultiLoggingWatcherSectionError() throws IOException { + String solrXml = "" + + "42" + + "42" + + "42" + + ""; + + expectedException.expect(SolrException.class); + expectedException.expectMessage("3 instances of Logging found in solr.xml"); + ConfigSolr cfg = ConfigSolr.fromString(loader, solrXml); + } + + public void testValidStringValueWhenBoolTypeIsExpected() throws IOException { + boolean genericCoreNodeNames = random().nextBoolean(); + String solrXml = String.format(Locale.ROOT, "%s", genericCoreNodeNames); + + ConfigSolr configSolr = ConfigSolr.fromString(loader, solrXml); + assertEquals("gen core node names", genericCoreNodeNames, configSolr.getGenericCoreNodeNames()); + } + + public void testValidStringValueWhenIntTypeIsExpected() throws IOException { + int maxUpdateConnections = random().nextInt(); + String solrXml = String.format(Locale.ROOT, "%d", maxUpdateConnections); + ConfigSolr configSolr = ConfigSolr.fromString(loader, solrXml); + assertEquals("max update conn", maxUpdateConnections, configSolr.getMaxUpdateConnections()); + } + + public void testFailAtConfigParseTimeWhenIntTypeIsExpectedAndLongTypeIsGiven() throws IOException { + long val = TestUtil.nextLong(random(), Integer.MAX_VALUE, Long.MAX_VALUE); + String solrXml = String.format(Locale.ROOT, "%d", val); + + expectedException.expect(SolrException.class); + expectedException.expectMessage(String.format(Locale.ROOT, "Value of '%d' can not be parsed as 'int'", val)); + ConfigSolr configSolr = ConfigSolr.fromString(loader, solrXml); + } + + public void testFailAtConfigParseTimeWhenBoolTypeIsExpectedAndLongTypeIsGiven() throws IOException { + long val = random().nextLong(); + String solrXml = String.format(Locale.ROOT, "%d", val); + + expectedException.expect(SolrException.class); + expectedException.expectMessage(String.format(Locale.ROOT, "Value of '%d' can not be parsed as 'bool'", val)); + ConfigSolr configSolr = ConfigSolr.fromString(loader, solrXml); + } + + public void testFailAtConfigParseTimeWhenBoolTypeIsExpectedAndDoubleTypeIsGiven() throws IOException { + String val = ""+random().nextDouble(); + String solrXml = String.format(Locale.ROOT, "%s", val); + + expectedException.expect(SolrException.class); + expectedException.expectMessage(String.format(Locale.ROOT, "Value of '%s' can not be parsed as 'bool'", val)); + ConfigSolr configSolr = ConfigSolr.fromString(loader, solrXml); + } + + public void testFailAtConfigParseTimeWhenBoolTypeIsExpectedAndValueIsInvalidString() throws IOException { + String solrXml = "NOT_A_BOOLEAN"; + + expectedException.expect(SolrException.class); + expectedException.expectMessage("invalid boolean value: NOT_A_BOOLEAN"); + ConfigSolr configSolr = ConfigSolr.fromString(loader, solrXml); + } + + public void testFailAtConfigParseTimeWhenIntTypeIsExpectedAndBoolTypeIsGiven() throws IOException { + // given: + boolean randomBoolean = random().nextBoolean(); + String solrXml = String.format(Locale.ROOT, "%s", randomBoolean); + + expectedException.expect(SolrException.class); + expectedException.expectMessage(String.format(Locale.ROOT, "Value of 'unknown-option' can not be parsed as 'int': \"%s\"", randomBoolean)); + + ConfigSolr configSolr = ConfigSolr.fromString(loader, solrXml); + } + + public void testFailAtConfigParseTimeWhenUnrecognizedSolrCloudOptionWasFound() throws IOException { + String solrXml = "true"; + + expectedException.expect(SolrException.class); + expectedException.expectMessage(" section of solr.xml contains 1 unknown config parameter(s): [unknown-option]"); + + ConfigSolr configSolr = ConfigSolr.fromString(loader, solrXml); + } + + public void testFailAtConfigParseTimeWhenUnrecognizedSolrOptionWasFound() throws IOException { + String solrXml = "truetrue"; + + expectedException.expect(SolrException.class); + expectedException.expectMessage("Main section of solr.xml contains 2 unknown config parameter(s): [unknown-bool-option, unknown-str-option]"); + + ConfigSolr configSolr = ConfigSolr.fromString(loader, solrXml); + } + + public void testFailAtConfigParseTimeWhenUnrecognizedLoggingOptionWasFound() throws IOException { + String solrXml = String.format(Locale.ROOT, "%s", random().nextBoolean()); + + expectedException.expect(SolrException.class); + expectedException.expectMessage(" section of solr.xml contains 1 unknown config parameter(s): [unknown-option]"); + + ConfigSolr configSolr = ConfigSolr.fromString(loader, solrXml); + } + + public void testFailAtConfigParseTimeWhenLoggingConfigParamsAreDuplicated() throws IOException { + String v1 = ""+random().nextInt(); + String v2 = ""+random().nextInt(); + String solrXml = String.format(Locale.ROOT, + "" + + "%s" + + "%s" + + "", + v1, v2); + + expectedException.expect(SolrException.class); + expectedException.expectMessage(String.format(Locale.ROOT, " section of solr.xml contains duplicated 'class' in solr.xml: [%s, %s]", v1, v2)); + + ConfigSolr configSolr = ConfigSolr.fromString(loader, solrXml); + } + + public void testFailAtConfigParseTimeWhenSolrCloudConfigParamsAreDuplicated() throws IOException { + String v1 = ""+random().nextInt(); + String v2 = ""+random().nextInt(); + String v3 = ""+random().nextInt(); + String solrXml = String.format(Locale.ROOT, + "" + + "%s" + + "%s" + + "foo" + // other ok val in middle + "%s" + + "", + v1, v2, v3); + + expectedException.expect(SolrException.class); + expectedException.expectMessage(String.format(Locale.ROOT, " section of solr.xml contains duplicated 'zkClientTimeout' in solr.xml: [%s, %s, %s]", v1, v2, v3)); + + ConfigSolr configSolr = ConfigSolr.fromString(loader, solrXml); + } + + public void testFailAtConfigParseTimeWhenSolrConfigParamsAreDuplicated() throws IOException { + String v1 = ""+random().nextInt(); + String v2 = ""+random().nextInt(); + String solrXml = String.format(Locale.ROOT, + "" + + "%s" + + "%s" + + "", + v1, v2); + + expectedException.expect(SolrException.class); + expectedException.expectMessage(String.format(Locale.ROOT, "Main section of solr.xml contains duplicated 'coreLoadThreads' in solr.xml: [%s, %s]", v1, v2)); + + ConfigSolr configSolr = ConfigSolr.fromString(loader, solrXml); } }