SOLR-7226: Make /query/* jmx/* , requestDispatcher/*, <listener> <initParams> properties in solrconfig.xml editable

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1669368 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Noble Paul 2015-03-26 16:39:14 +00:00
parent f2874dd9d3
commit ad913e4013
8 changed files with 607 additions and 389 deletions

View File

@ -221,6 +221,11 @@ New Features
Example: q=price:[ ${low:0} TO ${high} ]&low=100&high=200
(yonik)
* SOLR-7226: Make /query/* jmx/* , requestDispatcher/*, <listener> <initParams>
properties in solrconfig.xml editable (Noble Paul)
Bug Fixes
----------------------

View File

@ -19,7 +19,6 @@ package org.apache.solr.core;
import java.io.IOException;
import java.io.StringReader;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
@ -30,16 +29,15 @@ import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.request.SolrRequestHandler;
import org.noggit.CharArr;
import org.noggit.JSONParser;
import org.noggit.JSONWriter;
import org.noggit.ObjectBuilder;
/**This class encapsulates the config overlay json file. It is immutable
/**
* This class encapsulates the config overlay json file. It is immutable
* and any edit operations performed on tbhis gives a new copy of the object
* with the changed value
*
*/
public class ConfigOverlay implements MapSerializable {
private final int znodeVersion;
@ -56,6 +54,7 @@ public class ConfigOverlay implements MapSerializable{
userProps = (Map<String, Object>) data.get("userProps");
if (userProps == null) userProps = Collections.EMPTY_MAP;
}
public Object getXPathProperty(String xpath) {
return getXPathProperty(xpath, true);
}
@ -93,6 +92,7 @@ public class ConfigOverlay implements MapSerializable{
jsonObj.put("userProps", copy);
return new ConfigOverlay(jsonObj, znodeVersion);
}
public ConfigOverlay unsetUserProperty(String key) {
if (!userProps.containsKey(key)) return this;
Map copy = new LinkedHashMap(userProps);
@ -125,7 +125,6 @@ public class ConfigOverlay implements MapSerializable{
}
private Map getDeepCopy(Map map) {
return (Map) ZkStateReader.fromJSON(ZkStateReader.toJSON(map));
}
@ -135,7 +134,8 @@ public class ConfigOverlay implements MapSerializable{
private List<String> checkEditable(String propName, boolean isXPath, boolean failOnError) {
LinkedList<String> hierarchy = new LinkedList<>();
if (!isEditableProp(propName, isXPath, hierarchy)) {
if(failOnError) throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, StrUtils.formatString( NOT_EDITABLE,propName));
if (failOnError)
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, StrUtils.formatString(NOT_EDITABLE, propName));
else return null;
}
return hierarchy;
@ -187,23 +187,73 @@ public class ConfigOverlay implements MapSerializable{
public static final String RESOURCE_NAME = "configoverlay.json";
private static final Long XML_ATTR = 0L;
private static final Long XML_NODE = 1L;
private static final Long STR_ATTR = 0L;
private static final Long STR_NODE = 1L;
private static final Long BOOL_ATTR = 10L;
private static final Long BOOL_NODE = 11L;
private static final Long INT_ATTR = 20L;
private static final Long INT_NODE = 21L;
private static final Long FLOAT_ATTR = 30L;
private static final Long FLOAT_NODE = 31L;
private static Map editable_prop_map;
public static final String MAPPING = "{ updateHandler : {" +
" autoCommit : { maxDocs:1, maxTime:1, openSearcher:1 }," +
" autoSoftCommit : { maxDocs:1, maxTime :1}," +
" commitWithin : {softCommit:1}," +
" commitIntervalLowerBound:1," +
" indexWriter : {closeWaitsForMerges:1}" +
" }," +
//The path maps to the xml xpath and value of 1 means it is a tag with a string value and value
// of 0 means it is an attribute with string value
public static final String MAPPING = "{" +
" updateHandler:{" +
" autoCommit:{" +
" maxDocs:20," +
" maxTime:20," +
" openSearcher:11}," +
" autoSoftCommit:{" +
" maxDocs:20," +
" maxTime:20}," +
" commitWithin:{softCommit:11}," +
" commitIntervalLowerBound:21," +
" indexWriter:{closeWaitsForMerges:11}}," +
" query:{" +
" filterCache : {class:0, size:0, initialSize:0 , autowarmCount:0 , regenerator:0}," +
" queryResultCache :{class:0, size:0, initialSize:0,autowarmCount:0,regenerator:0}," +
" documentCache :{class:0, size:0, initialSize:0 ,autowarmCount:0,regenerator:0}," +
" fieldValueCache :{class:0, size:0, initialSize:0 ,autowarmCount:0,regenerator:0}" +
"}}";
" filterCache:{" +
" class:0," +
" size:0," +
" initialSize:20," +
" autowarmCount:20," +
" regenerator:0}," +
" queryResultCache:{" +
" class:0," +
" size:20," +
" initialSize:20," +
" autowarmCount:20," +
" regenerator:0}," +
" documentCache:{" +
" class:0," +
" size:20," +
" initialSize:20," +
" autowarmCount:20," +
" regenerator:0}," +
" fieldValueCache:{" +
" class:0," +
" size:20," +
" initialSize:20," +
" autowarmCount:20," +
" regenerator:0}," +
" useFilterForSortedQuery:1," +
" queryResultWindowSize:1," +
" queryResultMaxDocsCached:1," +
" enableLazyFieldLoading:1," +
" boolTofilterOptimizer:1," +
" maxBooleanClauses:1}," +
" jmx:{" +
" agentId:0," +
" serviceUrl:0," +
" rootName:0}," +
" requestDispatcher:{" +
" handleSelect:0," +
" requestParsers:{" +
" multipartUploadLimitInKB:0," +
" formdataUploadLimitInKB:0," +
" enableRemoteStreaming:0," +
" addHttpRequestToContext:0}}}";
static {
try {
editable_prop_map = (Map) new ObjectBuilder(new JSONParser(new StringReader(
@ -213,8 +263,12 @@ public class ConfigOverlay implements MapSerializable{
}
}
public static boolean isEditableProp(String path, boolean isXpath, List<String> hierarchy) {
return !(checkEditable(path, isXpath, hierarchy) == null);
}
public static Class checkEditable(String path, boolean isXpath, List<String> hierarchy) {
List<String> parts = StrUtils.splitSmart(path, isXpath ? '/' : '.');
Object obj = editable_prop_map;
for (int i = 0; i < parts.size(); i++) {
@ -224,23 +278,33 @@ public class ConfigOverlay implements MapSerializable{
part = part.substring(1);
}
if (hierarchy != null) hierarchy.add(part);
if(obj ==null) return false;
if (obj == null) return null;
if (i == parts.size() - 1) {
if (obj instanceof Map) {
Map map = (Map) obj;
if(isXpath && isAttr){
return XML_ATTR.equals(map.get(part));
} else {
return XML_ATTR.equals( map.get(part)) || XML_NODE.equals(map.get(part));
Object o = map.get(part);
return checkType(o, isXpath, isAttr);
}
}
return false;
return null;
}
obj = ((Map) obj).get(part);
}
return false;
return null;
}
static Class[] types = new Class[]{String.class, Boolean.class, Integer.class, Float.class};
private static Class checkType(Object o, boolean isXpath, boolean isAttr) {
if (o instanceof Long) {
Long aLong = (Long) o;
int ten = aLong.intValue() / 10;
int one = aLong.intValue() % 10;
if (isXpath && isAttr && one != 0) return null;
return types[ten];
} else {
return null;
}
}
public Map<String, String> getEditableSubProperties(String xpath) {
Object o = getObjectByPath(props, false, StrUtils.splitSmart(xpath, '/'));
@ -262,6 +326,7 @@ public class ConfigOverlay implements MapSerializable{
result.putAll(data);
return result;
}
public Map<String, Map> getNamedPlugins(String typ) {
Map<String, Map> reqHandlers = (Map<String, Map>) data.get(typ);
if (reqHandlers == null) return Collections.EMPTY_MAP;
@ -285,7 +350,11 @@ public class ConfigOverlay implements MapSerializable{
return new ConfigOverlay(dataCopy, this.znodeVersion);
}
public static final String ZNODEVER = "znodeVersion";
public static final String NAME = "overlay";
public static void main(String[] args) {
}
}

View File

@ -20,7 +20,6 @@ package org.apache.solr.core;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -57,7 +56,9 @@ public class PluginBag<T> implements AutoCloseable {
private SolrCore core;
private final SolrConfig.SolrPluginInfo meta;
/** Pass needThreadSafety=true if plugins can be added and removed concurrently with lookups. */
/**
* Pass needThreadSafety=true if plugins can be added and removed concurrently with lookups.
*/
public PluginBag(Class<T> klass, SolrCore core, boolean needThreadSafety) {
this.core = core;
this.klass = klass;
@ -72,12 +73,14 @@ public class PluginBag<T> implements AutoCloseable {
}
}
/** Constructs a non-threadsafe plugin registry */
/**
* Constructs a non-threadsafe plugin registry
*/
public PluginBag(Class<T> klass, SolrCore core) {
this(klass, core, false);
}
static void initInstance(Object inst, PluginInfo info, SolrCore core) {
static void initInstance(Object inst, PluginInfo info) {
if (inst instanceof PluginInfoInitialized) {
((PluginInfoInitialized) inst).init(info);
} else if (inst instanceof NamedListInitializedPlugin) {
@ -94,16 +97,16 @@ public class PluginBag<T> implements AutoCloseable {
}
PluginHolder<T> createPlugin(PluginInfo info, SolrCore core) {
PluginHolder<T> createPlugin(PluginInfo info) {
if ("true".equals(String.valueOf(info.attributes.get("runtimeLib")))) {
log.info(" {} : '{}' created with runtimeLib=true ", meta.tag, info.name);
log.info(" {} : '{}' created with runtimeLib=true ", meta.getCleanTag(), info.name);
return new LazyPluginHolder<>(meta, info, core, core.getMemClassLoader());
} else if ("lazy".equals(info.attributes.get("startup")) && meta.options.contains(SolrConfig.PluginOpts.LAZY)) {
log.info("{} : '{}' created with startup=lazy ", meta.tag, info.name);
log.info("{} : '{}' created with startup=lazy ", meta.getCleanTag(), info.name);
return new LazyPluginHolder<T>(meta, info, core, core.getResourceLoader());
} else {
T inst = core.createInstance(info.className, (Class<T>) meta.clazz, meta.tag, null, core.getResourceLoader());
initInstance(inst, info, core);
T inst = core.createInstance(info.className, (Class<T>) meta.clazz, meta.getCleanTag(), null, core.getResourceLoader());
initInstance(inst, info);
return new PluginHolder<>(info, inst);
}
}
@ -164,7 +167,7 @@ public class PluginBag<T> implements AutoCloseable {
void setDefault(String def) {
if (!registry.containsKey(def)) return;
if (this.def != null) log.warn("Multiple defaults for : " + meta.tag);
if (this.def != null) log.warn("Multiple defaults for : " + meta.getCleanTag());
this.def = def;
}
@ -197,11 +200,11 @@ public class PluginBag<T> implements AutoCloseable {
void init(Map<String, T> defaults, SolrCore solrCore, List<PluginInfo> infos) {
core = solrCore;
for (PluginInfo info : infos) {
PluginHolder<T> o = createPlugin(info, solrCore);
PluginHolder<T> o = createPlugin(info);
String name = info.name;
if (meta.clazz.equals(SolrRequestHandler.class)) name = RequestHandlers.normalize(info.name);
PluginHolder<T> old = put(name, o);
if (old != null) log.warn("Multiple entries of {} with name {}", meta.tag, name);
if (old != null) log.warn("Multiple entries of {} with name {}", meta.getCleanTag(), name);
}
for (Map.Entry<String, T> e : defaults.entrySet()) {
if (!contains(e.getKey())) {
@ -238,7 +241,7 @@ public class PluginBag<T> implements AutoCloseable {
try {
e.getValue().close();
} catch (Exception exp) {
log.error("Error closing plugin " + e.getKey() + " of type : " + meta.tag, exp);
log.error("Error closing plugin " + e.getKey() + " of type : " + meta.getCleanTag(), exp);
}
}
}
@ -331,14 +334,14 @@ public class PluginBag<T> implements AutoCloseable {
private synchronized boolean createInst() {
if (lazyInst != null) return false;
log.info("Going to create a new {} with {} ", pluginMeta.tag, pluginInfo.toString());
log.info("Going to create a new {} with {} ", pluginMeta.getCleanTag(), pluginInfo.toString());
if (resourceLoader instanceof MemClassLoader) {
MemClassLoader loader = (MemClassLoader) resourceLoader;
loader.loadJars();
}
Class<T> clazz = (Class<T>) pluginMeta.clazz;
T localInst = core.createInstance(pluginInfo.className, clazz, pluginMeta.tag, null, resourceLoader);
initInstance(localInst, pluginInfo, core);
T localInst = core.createInstance(pluginInfo.className, clazz, pluginMeta.getCleanTag(), null, resourceLoader);
initInstance(localInst, pluginInfo);
if (localInst instanceof SolrCoreAware) {
SolrResourceLoader.assertAwareCompatibility(SolrCoreAware.class, localInst);
((SolrCoreAware) localInst).inform(core);

View File

@ -73,6 +73,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -81,6 +82,7 @@ import static org.apache.solr.core.SolrConfig.PluginOpts.MULTI_OK;
import static org.apache.solr.core.SolrConfig.PluginOpts.NOOP;
import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_CLASS;
import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME;
import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME_IN_OVERLAY;
/**
@ -98,6 +100,7 @@ public class SolrConfig extends Config implements MapSerializable{
public static enum PluginOpts {
MULTI_OK,
REQUIRE_NAME,
REQUIRE_NAME_IN_OVERLAY,
REQUIRE_CLASS,
LAZY,
// EnumSet.of and/or EnumSet.copyOf(Collection) are anoying
@ -117,14 +120,18 @@ public class SolrConfig extends Config implements MapSerializable{
private final SolrRequestParsers solrRequestParsers;
/** Creates a default instance from the solrconfig.xml. */
/**
* Creates a default instance from the solrconfig.xml.
*/
public SolrConfig()
throws ParserConfigurationException, IOException, SAXException {
this((SolrResourceLoader) null, DEFAULT_CONF_FILE, null);
}
/** Creates a configuration instance from a configuration name.
/**
* Creates a configuration instance from a configuration name.
* A default resource loader will be created (@see SolrResourceLoader)
*
* @param name the configuration name used by the loader
*/
public SolrConfig(String name)
@ -132,10 +139,12 @@ public class SolrConfig extends Config implements MapSerializable{
this((SolrResourceLoader) null, name, null);
}
/** Creates a configuration instance from a configuration name and stream.
/**
* Creates a configuration instance from a configuration name and stream.
* A default resource loader will be created (@see SolrResourceLoader).
* If the stream is null, the resource loader will open the configuration stream.
* If the stream is not null, no attempt to load the resource will occur (the name is not used).
*
* @param name the configuration name
* @param is the configuration stream
*/
@ -144,7 +153,9 @@ public class SolrConfig extends Config implements MapSerializable{
this((SolrResourceLoader) null, name, is);
}
/** Creates a configuration instance from an instance directory, configuration name and stream.
/**
* Creates a configuration instance from an instance directory, configuration name and stream.
*
* @param instanceDir the directory used to create the resource loader
* @param name the configuration name used by the loader if the stream is null
* @param is the configuration stream
@ -157,8 +168,7 @@ public class SolrConfig extends Config implements MapSerializable{
public static SolrConfig readFromResourceLoader(SolrResourceLoader loader, String name) {
try {
return new SolrConfig(loader, name, null);
}
catch (Exception e) {
} catch (Exception e) {
String resource;
if (loader instanceof ZkSolrResourceLoader) {
resource = name;
@ -169,9 +179,11 @@ public class SolrConfig extends Config implements MapSerializable{
}
}
/** Creates a configuration instance from a resource loader, a configuration name and a stream.
/**
* Creates a configuration instance from a resource loader, a configuration name and a stream.
* If the stream is null, the resource loader will open the configuration stream.
* If the stream is not null, no attempt to load the resource will occur (the name is not used).
*
* @param loader the resource loader
* @param name the configuration name
* @param is the configuration stream
@ -281,7 +293,7 @@ public class SolrConfig extends Config implements MapSerializable{
addHttpRequestToContext = getBool(
"requestDispatcher/requestParsers/@addHttpRequestToContext", false);
List<PluginInfo> argsInfos = pluginStore.get(InitParams.class.getName()) ;
List<PluginInfo> argsInfos = getPluginInfos(InitParams.class.getName());
if (argsInfos != null) {
Map<String, InitParams> argsMap = new HashMap<>();
for (PluginInfo p : argsInfos) {
@ -314,7 +326,9 @@ public class SolrConfig extends Config implements MapSerializable{
// regardless of when/how/why they are used (or even if they are
// declared outside of the appropriate context) but there's no nice
// way around that in the PluginInfo framework
.add(new SolrPluginInfo(SolrEventListener.class, "//listener", REQUIRE_CLASS, MULTI_OK))
.add(new SolrPluginInfo(InitParams.class, InitParams.TYPE, MULTI_OK, REQUIRE_NAME_IN_OVERLAY))
.add(new SolrPluginInfo(SolrEventListener.class, "//listener", REQUIRE_CLASS, MULTI_OK, REQUIRE_NAME_IN_OVERLAY))
.add(new SolrPluginInfo(DirectoryFactory.class, "directoryFactory", REQUIRE_CLASS))
.add(new SolrPluginInfo(IndexDeletionPolicy.class, "indexConfig/deletionPolicy", REQUIRE_CLASS))
.add(new SolrPluginInfo(CodecFactory.class, "codecFactory", REQUIRE_CLASS))
@ -323,10 +337,10 @@ public class SolrConfig extends Config implements MapSerializable{
.add(new SolrPluginInfo(UpdateLog.class, "updateHandler/updateLog"))
.add(new SolrPluginInfo(IndexSchemaFactory.class, "schemaFactory", REQUIRE_CLASS))
.add(new SolrPluginInfo(RestManager.class, "restManager"))
.add(new SolrPluginInfo(InitParams.class, InitParams.TYPE, MULTI_OK))
.add(new SolrPluginInfo(StatsCache.class, "statsCache", REQUIRE_CLASS))
.build();
public static final Map<String, SolrPluginInfo> classVsSolrPluginInfo;
static {
Map<String, SolrPluginInfo> map = new HashMap<>();
for (SolrPluginInfo plugin : plugins) map.put(plugin.clazz.getName(), plugin);
@ -345,6 +359,15 @@ public class SolrConfig extends Config implements MapSerializable{
this.tag = tag;
this.options = opts == null ? Collections.EMPTY_SET : EnumSet.of(NOOP, opts);
}
public String getCleanTag() {
return tag.replaceAll("/", "");
}
public String getTagCleanLower() {
return getCleanTag().toLowerCase(Locale.ROOT);
}
}
public static ConfigOverlay getConfigOverlay(SolrResourceLoader loader) {
@ -371,9 +394,11 @@ public class SolrConfig extends Config implements MapSerializable{
}
private Map<String, InitParams> initParams = Collections.emptyMap();
public Map<String, InitParams> getInitParams() {
return initParams;
}
protected UpdateHandlerInfo loadUpdatehandlerInfo() {
return new UpdateHandlerInfo(get("updateHandler/@class", null),
getInt("updateHandler/autoCommit/maxDocs", -1),
@ -396,7 +421,7 @@ public class SolrConfig extends Config implements MapSerializable{
throw new SolrException
(SolrException.ErrorCode.SERVER_ERROR,
"Found " + result.size() + " configuration sections when at most "
+ "1 is allowed matching expression: " + pluginInfo.tag);
+ "1 is allowed matching expression: " + pluginInfo.getCleanTag());
}
if (!result.isEmpty()) pluginStore.put(pluginInfo.clazz.getName(), result);
}
@ -458,6 +483,7 @@ public class SolrConfig extends Config implements MapSerializable{
public final JmxConfiguration jmxConfig;
private final HttpCachingConfig httpCachingConfig;
public HttpCachingConfig getHttpCachingConfig() {
return httpCachingConfig;
}
@ -498,11 +524,15 @@ public class SolrConfig extends Config implements MapSerializable{
public static class HttpCachingConfig implements MapSerializable {
/** config xpath prefix for getting HTTP Caching options */
/**
* config xpath prefix for getting HTTP Caching options
*/
private final static String CACHE_PRE
= "requestDispatcher/httpCaching/";
/** For extracting Expires "ttl" from <cacheControl> config */
/**
* For extracting Expires "ttl" from <cacheControl> config
*/
private final static Pattern MAX_AGE
= Pattern.compile("\\bmax-age=(\\d+)");
@ -517,7 +547,9 @@ public class SolrConfig extends Config implements MapSerializable{
public static enum LastModFrom {
OPENTIME, DIRLASTMOD, BOGUS;
/** Input must not be null */
/**
* Input must not be null
*/
public static LastModFrom parse(final String s) {
try {
return valueOf(s.toUpperCase(Locale.ROOT));
@ -564,13 +596,31 @@ public class SolrConfig extends Config implements MapSerializable{
}
public boolean isNever304() { return never304; }
public String getEtagSeed() { return etagSeed; }
/** null if no Cache-Control header */
public String getCacheControlHeader() { return cacheControlHeader; }
/** null if no max age limitation */
public Long getMaxAge() { return maxAge; }
public LastModFrom getLastModFrom() { return lastModFrom; }
public boolean isNever304() {
return never304;
}
public String getEtagSeed() {
return etagSeed;
}
/**
* null if no Cache-Control header
*/
public String getCacheControlHeader() {
return cacheControlHeader;
}
/**
* null if no max age limitation
*/
public Long getMaxAge() {
return maxAge;
}
public LastModFrom getLastModFrom() {
return lastModFrom;
}
}
public static class UpdateHandlerInfo implements MapSerializable {
@ -602,7 +652,6 @@ public class SolrConfig extends Config implements MapSerializable{
}
@Override
public Map<String, Object> toMap() {
LinkedHashMap result = new LinkedHashMap();
@ -626,11 +675,17 @@ public class SolrConfig extends Config implements MapSerializable{
// public Map<String, List<PluginInfo>> getUpdateProcessorChainInfo() { return updateProcessorChainInfo; }
public UpdateHandlerInfo getUpdateHandlerInfo() { return updateHandlerInfo; }
public UpdateHandlerInfo getUpdateHandlerInfo() {
return updateHandlerInfo;
}
public String getDataDir() { return dataDir; }
public String getDataDir() {
return dataDir;
}
/**SolrConfig keeps a repository of plugins by the type. The known interfaces are the types.
/**
* SolrConfig keeps a repository of plugins by the type. The known interfaces are the types.
*
* @param type The key is FQN of the plugin class there are a few known types : SolrFormatter, SolrFragmenter
* SolrRequestHandler,QParserPlugin, QueryResponseWriter,ValueSourceParser,
* SearchComponent, QueryConverter, SolrEventListener, DirectoryFactory,
@ -639,19 +694,27 @@ public class SolrConfig extends Config implements MapSerializable{
public List<PluginInfo> getPluginInfos(String type) {
List<PluginInfo> result = pluginStore.get(type);
SolrPluginInfo info = classVsSolrPluginInfo.get(type);
if (info != null && info.options.contains(REQUIRE_NAME)) {
Map<String, Map> infos = overlay.getNamedPlugins(info.tag);
if (info != null &&
(info.options.contains(REQUIRE_NAME) || info.options.contains(REQUIRE_NAME_IN_OVERLAY))) {
Map<String, Map> infos = overlay.getNamedPlugins(info.getCleanTag());
if (!infos.isEmpty()) {
LinkedHashMap<String, PluginInfo> map = new LinkedHashMap<>();
if (result != null) for (PluginInfo pluginInfo : result) map.put(pluginInfo.name, pluginInfo);
if (result != null) for (PluginInfo pluginInfo : result) {
//just create a UUID for the time being so that map key is not null
String name = pluginInfo.name == null ?
UUID.randomUUID().toString().toLowerCase(Locale.ROOT) :
pluginInfo.name;
map.put(name, pluginInfo);
}
for (Map.Entry<String, Map> e : infos.entrySet()) {
map.put(e.getKey(), new PluginInfo(info.tag, e.getValue()));
map.put(e.getKey(), new PluginInfo(info.getCleanTag(), e.getValue()));
}
result = new ArrayList<>(map.values());
}
}
return result == null ? Collections.<PluginInfo>emptyList() : result;
}
public PluginInfo getPluginInfo(String type) {
List<PluginInfo> result = pluginStore.get(type);
if (result == null || result.isEmpty()) {
@ -728,18 +791,31 @@ public class SolrConfig extends Config implements MapSerializable{
@Override
public int getInt(String path, int def) {
Object v = overlay.getXPathProperty(path);
Object val = overlay.getXPathProperty(path);
if (val != null) return Integer.parseInt(val.toString());
return super.getInt(path, def);
}
@Override
public boolean getBool(String path, boolean def) {
Object val = overlay.getXPathProperty(path);
if (val != null) return Boolean.parseBoolean(val.toString());
return super.getBool(path, def);
}
@Override
public String get(String path) {
Object val = overlay.getXPathProperty(path, true);
return val != null ? val.toString() : super.get(path);
}
@Override
public String get(String path, String def) {
Object val = overlay.getXPathProperty(path, true);
return val != null ? val.toString() : super.get(path, def);
}
@Override
public Map<String, Object> toMap() {
LinkedHashMap result = new LinkedHashMap();
@ -753,11 +829,11 @@ public class SolrConfig extends Config implements MapSerializable{
m.put("queryResultMaxDocsCached", queryResultMaxDocsCached);
m.put("enableLazyFieldLoading", enableLazyFieldLoading);
m.put("maxBooleanClauses", booleanQueryMaxClauseCount);
if (jmxConfig != null) result.put("jmx", jmxConfig.toMap());
for (SolrPluginInfo plugin : plugins) {
List<PluginInfo> infos = getPluginInfos(plugin.clazz.getName());
if (infos == null || infos.isEmpty()) continue;
String tag = plugin.tag;
String tag = plugin.getCleanTag();
tag = tag.replace("/", "");
if (plugin.options.contains(PluginOpts.REQUIRE_NAME)) {
LinkedHashMap items = new LinkedHashMap();
@ -808,6 +884,7 @@ public class SolrConfig extends Config implements MapSerializable{
result.putAll(p);
return result;
}
private ConfigOverlay overlay;
public ConfigOverlay getOverlay() {

View File

@ -19,7 +19,6 @@ package org.apache.solr.handler;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -57,9 +56,11 @@ import org.slf4j.LoggerFactory;
import static java.util.Collections.singletonList;
import static org.apache.solr.common.params.CoreAdminParams.NAME;
import static org.apache.solr.common.util.StrUtils.formatString;
import static org.apache.solr.core.ConfigOverlay.NOT_EDITABLE;
import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_CLASS;
import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME;
import static org.apache.solr.core.SolrConfig.PluginOpts.REQUIRE_NAME_IN_OVERLAY;
import static org.apache.solr.schema.FieldType.CLASS_NAME;
public class SolrConfigHandler extends RequestHandlerBase {
@ -70,9 +71,8 @@ public class SolrConfigHandler extends RequestHandlerBase {
static {
Map<String, SolrConfig.SolrPluginInfo> map = new HashMap<>();
for (SolrConfig.SolrPluginInfo plugin : SolrConfig.plugins) {
if (plugin.options.contains(REQUIRE_NAME)) {
map.put(plugin.tag.toLowerCase(Locale.ROOT), plugin);
if (plugin.options.contains(REQUIRE_NAME) || plugin.options.contains(REQUIRE_NAME_IN_OVERLAY)) {
map.put(plugin.getCleanTag().toLowerCase(Locale.ROOT), plugin);
}
}
namedPlugins = Collections.unmodifiableMap(map);
@ -252,7 +252,7 @@ public class SolrConfigHandler extends RequestHandlerBase {
if (op.hasError()) break;
for (String s : name) {
if (params.getParams(s) == null) {
op.addError(StrUtils.formatString("can't delete . No such params ''{0}'' exist", s));
op.addError(formatString("can't delete . No such params ''{0}'' exist", s));
}
params = params.setParams(s, null);
}
@ -302,19 +302,19 @@ public class SolrConfigHandler extends RequestHandlerBase {
default: {
List<String> pcs = StrUtils.splitSmart(op.name.toLowerCase(Locale.ROOT), '-');
if (pcs.size() != 2) {
op.addError(StrUtils.formatString("Unknown operation ''{0}'' ", op.name));
op.addError(formatString("Unknown operation ''{0}'' ", op.name));
} else {
String prefix = pcs.get(0);
String name = pcs.get(1);
if (cmdPrefixes.contains(prefix) && namedPlugins.containsKey(name)) {
SolrConfig.SolrPluginInfo info = namedPlugins.get(name);
if ("delete".equals(prefix)) {
overlay = deleteNamedComponent(op, overlay, info.tag);
overlay = deleteNamedComponent(op, overlay, info.getCleanTag());
} else {
overlay = updateNamedPlugin(info, op, overlay, prefix.equals("create") || prefix.equals("add"));
}
} else {
op.addError(StrUtils.formatString("Unknown operation ''{0}'' ", op.name));
op.addError(formatString("Unknown operation ''{0}'' ", op.name));
}
}
}
@ -347,7 +347,7 @@ public class SolrConfigHandler extends RequestHandlerBase {
if (overlay.getNamedPlugins(typ).containsKey(name)) {
return overlay.deleteNamedPlugin(name, typ);
} else {
op.addError(StrUtils.formatString("NO such {0} ''{1}'' ", typ, name));
op.addError(formatString("NO such {0} ''{1}'' ", typ, name));
return overlay;
}
}
@ -360,18 +360,18 @@ public class SolrConfigHandler extends RequestHandlerBase {
op.getMap(PluginInfo.APPENDS, null);
if (op.hasError()) return overlay;
if (!verifyClass(op, clz, info.clazz)) return overlay;
if (overlay.getNamedPlugins(info.tag).containsKey(name)) {
if (overlay.getNamedPlugins(info.getCleanTag()).containsKey(name)) {
if (isCeate) {
op.addError(StrUtils.formatString(" ''{0}'' already exists . Do an ''{1}'' , if you want to change it ", name, "update-" + info.tag.toLowerCase(Locale.ROOT)));
op.addError(formatString(" ''{0}'' already exists . Do an ''{1}'' , if you want to change it ", name, "update-" + info.getTagCleanLower()));
return overlay;
} else {
return overlay.addNamedPlugin(op.getDataMap(), info.tag);
return overlay.addNamedPlugin(op.getDataMap(), info.getCleanTag());
}
} else {
if (isCeate) {
return overlay.addNamedPlugin(op.getDataMap(), info.tag);
return overlay.addNamedPlugin(op.getDataMap(), info.getCleanTag());
} else {
op.addError(StrUtils.formatString(" ''{0}'' does not exist . Do an ''{1}'' , if you want to create it ", name, "create-" + info.tag.toLowerCase(Locale.ROOT)));
op.addError(formatString(" ''{0}'' does not exist . Do an ''{1}'' , if you want to create it ", name, "create-" + info.getTagCleanLower()));
return overlay;
}
}
@ -408,7 +408,7 @@ public class SolrConfigHandler extends RequestHandlerBase {
if (op.hasError()) return overlay;
for (String o : name) {
if (!overlay.getUserProps().containsKey(o)) {
op.addError(StrUtils.formatString("No such property ''{0}''", name));
op.addError(formatString("No such property ''{0}''", name));
} else {
overlay = overlay.unsetUserProperty(o);
}
@ -423,7 +423,7 @@ public class SolrConfigHandler extends RequestHandlerBase {
for (String o : name) {
if (!ConfigOverlay.isEditableProp(o, false, null)) {
op.addError(StrUtils.formatString(NOT_EDITABLE, name));
op.addError(formatString(NOT_EDITABLE, name));
} else {
overlay = overlay.unsetProperty(o);
}
@ -437,10 +437,42 @@ public class SolrConfigHandler extends RequestHandlerBase {
for (Map.Entry<String, Object> e : m.entrySet()) {
String name = e.getKey();
Object val = e.getValue();
if (!ConfigOverlay.isEditableProp(name, false, null)) {
op.addError(StrUtils.formatString(NOT_EDITABLE, name));
Class typ = ConfigOverlay.checkEditable(name, false, null);
if (typ == null) {
op.addError(formatString(NOT_EDITABLE, name));
continue;
}
if (val != null) {
if (typ == String.class) val = val.toString();
String typeErr = "Property {0} must be of {1} type ";
if (typ == Boolean.class) {
try {
val = Boolean.parseBoolean(val.toString());
} catch (Exception exp) {
op.addError(formatString(typeErr, name, typ.getSimpleName()));
continue;
}
} else if (typ == Integer.class) {
try {
val = Integer.parseInt(val.toString());
} catch (Exception exp) {
op.addError(formatString(typeErr, typ.getSimpleName()));
continue;
}
} else if (typ == Float.class) {
try {
val = Float.parseFloat(val.toString());
} catch (Exception exp) {
op.addError(formatString(typeErr, typ.getSimpleName()));
continue;
}
}
}
overlay = overlay.setProperty(name, val);
}
return overlay;
@ -459,7 +491,7 @@ public class SolrConfigHandler extends RequestHandlerBase {
c == '.'
) continue;
else {
return StrUtils.formatString("''{0}'' name should only have chars [a-zA-Z_-.0-9] ", s);
return formatString("''{0}'' name should only have chars [a-zA-Z_-.0-9] ", s);
}
}
return null;
@ -487,7 +519,7 @@ public class SolrConfigHandler extends RequestHandlerBase {
static {
for (SolrConfig.SolrPluginInfo solrPluginInfo : SolrConfig.plugins)
subPaths.add("/" + solrPluginInfo.tag.replaceAll("/", ""));
subPaths.add("/" + solrPluginInfo.getCleanTag());
}

View File

@ -42,6 +42,21 @@ public class TestConfigOverlay extends LuceneTestCase {
assertTrue(isEditableProp("updateHandler.autoCommit.maxTime", false, null));
assertTrue(isEditableProp("updateHandler.commitWithin.softCommit", false, null));
assertTrue(isEditableProp("updateHandler.indexWriter.closeWaitsForMerges", false, null));
assertTrue(isEditableProp("query.useFilterForSortedQuery", false, null));
assertTrue(isEditableProp("query.queryResultWindowSize", false, null));
assertTrue(isEditableProp("query.queryResultMaxDocsCached", false, null));
assertTrue(isEditableProp("query.enableLazyFieldLoading", false, null));
assertTrue(isEditableProp("query.boolTofilterOptimizer", false, null));
assertTrue(isEditableProp("jmx.agentId", false, null));
assertTrue(isEditableProp("jmx.serviceUrl", false, null));
assertTrue(isEditableProp("jmx.rootName", false, null));
assertTrue(isEditableProp("requestDispatcher.requestParsers.multipartUploadLimitInKB", false, null));
assertTrue(isEditableProp("requestDispatcher.requestParsers.formdataUploadLimitInKB", false, null));
assertTrue(isEditableProp("requestDispatcher.requestParsers.enableRemoteStreaming", false, null));
assertTrue(isEditableProp("requestDispatcher.requestParsers.addHttpRequestToContext", false, null));
assertTrue(isEditableProp("requestDispatcher.handleSelect", false, null));
assertTrue(isEditableProp("updateHandler/commitIntervalLowerBound", true, null));
assertFalse(isEditableProp("updateHandler/commitIntervalLowerBound1", true, null));

View File

@ -107,7 +107,7 @@ public class TestSolrConfigHandler extends RestTestBase {
assertNotNull(getObjectByPath(confMap, false, Arrays.asList("config", "requestHandler", "/admin/ping")));
String payload = "{\n" +
" 'set-property' : { 'updateHandler.autoCommit.maxDocs':100, 'updateHandler.autoCommit.maxTime':10 } \n" +
" 'set-property' : { 'updateHandler.autoCommit.maxDocs':100, 'updateHandler.autoCommit.maxTime':10 , 'requestDispatcher.requestParsers.addHttpRequestToContext':true} \n" +
" }";
runConfigCommand(harness, "/config?wt=json", payload);
@ -122,6 +122,7 @@ public class TestSolrConfigHandler extends RestTestBase {
assertEquals("100", String.valueOf(getObjectByPath(m, true, ImmutableList.of("updateHandler", "autoCommit", "maxDocs"))));
assertEquals("10", String.valueOf(getObjectByPath(m, true, ImmutableList.of("updateHandler", "autoCommit", "maxTime"))));
assertEquals("true", String.valueOf(getObjectByPath(m, true, ImmutableList.of("requestDispatcher", "requestParsers", "addHttpRequestToContext"))));
payload = "{\n" +
" 'unset-property' : 'updateHandler.autoCommit.maxDocs'} \n" +
" }";
@ -366,6 +367,21 @@ public class TestSolrConfigHandler extends RestTestBase {
Arrays.asList("config", "transformer", "mytrans"),
null,
10);
payload = "{\n" +
"'create-initparams' : { 'name' : 'hello', 'key':'val'}\n" +
"}";
runConfigCommand(writeHarness, "/config?wt=json", payload);
Map map = testForResponseElement(writeHarness,
testServerBaseUrl,
"/config?wt=json",
cloudSolrServer,
Arrays.asList("config", "transformer", "mytrans"),
null,
10);
List l = (List) ConfigOverlay.getObjectByPath(map,false, Arrays.asList("config", "initParams"));
assertEquals( 1, l.size());
assertEquals( "val", ((Map)l.get(0)).get("key") );
}
@ -484,7 +500,6 @@ public class TestSolrConfigHandler extends RestTestBase {
5);
payload = " {\n" +
" 'set' : {'y':{\n" +
" 'c':'CY val',\n" +

View File

@ -306,6 +306,8 @@ public class StrUtils {
}
}
/**Format using MesssageFormat but with the ROOT locale
*/
public static String formatString(String pattern, Object... args) {
return new MessageFormat(pattern, Locale.ROOT).format(args);
}