blob: 0fa2bd78c64aba6d4f1270312e472488b0afe21b [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 java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.solr.common.MapSerializable;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.Utils;
import static org.apache.solr.common.util.Utils.toJSONString;
/**
* 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;
private final Map<String, Object> data;
private Map<String, Object> props;
private Map<String, Object> userProps;
@SuppressWarnings({"unchecked"})
public ConfigOverlay(Map<String, Object> jsonObj, int znodeVersion) {
if (jsonObj == null) jsonObj = Collections.EMPTY_MAP;
this.znodeVersion = znodeVersion;
data = Collections.unmodifiableMap(jsonObj);
props = (Map<String, Object>) data.get("props");
if (props == null) props = Collections.EMPTY_MAP;
userProps = (Map<String, Object>) data.get("userProps");
if (userProps == null) userProps = Collections.EMPTY_MAP;
}
public Object getXPathProperty(String xpath) {
return getXPathProperty(xpath, true);
}
public Object getXPathProperty(String xpath, boolean onlyPrimitive) {
List<String> hierarchy = checkEditable(xpath, true, false);
if (hierarchy == null) return null;
return Utils.getObjectByPath(props, onlyPrimitive, hierarchy);
}
public Object getXPathProperty(List<String> path) {
List<String> hierarchy = new ArrayList<>();
if(isEditable(true, hierarchy, path) == null) return null;
return Utils.getObjectByPath(props, true, hierarchy);
}
@SuppressWarnings({"unchecked"})
public ConfigOverlay setUserProperty(String key, Object val) {
@SuppressWarnings({"rawtypes"})
Map copy = new LinkedHashMap(userProps);
copy.put(key, val);
Map<String, Object> jsonObj = new LinkedHashMap<>(this.data);
jsonObj.put("userProps", copy);
return new ConfigOverlay(jsonObj, znodeVersion);
}
public ConfigOverlay unsetUserProperty(String key) {
if (!userProps.containsKey(key)) return this;
@SuppressWarnings({"unchecked", "rawtypes"})
Map copy = new LinkedHashMap(userProps);
copy.remove(key);
Map<String, Object> jsonObj = new LinkedHashMap<>(this.data);
jsonObj.put("userProps", copy);
return new ConfigOverlay(jsonObj, znodeVersion);
}
@SuppressWarnings({"unchecked", "rawtypes"})
public ConfigOverlay setProperty(String name, Object val) {
List<String> hierarchy = checkEditable(name, false, true);
Map deepCopy = (Map) Utils.fromJSON(Utils.toJSON(props));
Map obj = deepCopy;
for (int i = 0; i < hierarchy.size(); i++) {
String s = hierarchy.get(i);
if (i < hierarchy.size() - 1) {
if (obj.get(s) == null || (!(obj.get(s) instanceof Map))) {
obj.put(s, new LinkedHashMap<>());
}
obj = (Map) obj.get(s);
} else {
obj.put(s, val);
}
}
Map<String, Object> jsonObj = new LinkedHashMap<>(this.data);
jsonObj.put("props", deepCopy);
return new ConfigOverlay(jsonObj, znodeVersion);
}
public static final String NOT_EDITABLE = "''{0}'' is not an editable property";
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));
else return null;
}
return hierarchy;
}
@SuppressWarnings({"rawtypes"})
public ConfigOverlay unsetProperty(String name) {
List<String> hierarchy = checkEditable(name, false, true);
Map deepCopy = (Map) Utils.fromJSON(Utils.toJSON(props));
Map obj = deepCopy;
for (int i = 0; i < hierarchy.size(); i++) {
String s = hierarchy.get(i);
if (i < hierarchy.size() - 1) {
if (obj.get(s) == null || (!(obj.get(s) instanceof Map))) {
return this;
}
obj = (Map) obj.get(s);
} else {
obj.remove(s);
}
}
Map<String, Object> jsonObj = new LinkedHashMap<>(this.data);
jsonObj.put("props", deepCopy);
return new ConfigOverlay(jsonObj, znodeVersion);
}
public byte[] toByteArray() {
return Utils.toJSON(data);
}
public int getZnodeVersion() {
return znodeVersion;
}
@Override
public String toString() {
return toJSONString(data);
}
public static final String RESOURCE_NAME = "configoverlay.json";
/*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;*/
//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
@SuppressWarnings({"rawtypes"})
private static Map editable_prop_map = (Map) Utils.fromJSONResource("EditableSolrConfigAttributes.json");
public static boolean isEditableProp(String path, boolean isXpath, List<String> hierarchy) {
return !(checkEditable(path, isXpath, hierarchy) == null);
}
@SuppressWarnings({"rawtypes"})
public static Class checkEditable(String path, boolean isXpath, List<String> hierarchy) {
return isEditable(isXpath, hierarchy, StrUtils.splitSmart(path, isXpath ? '/' : '.'));
}
@SuppressWarnings("rawtypes")
private static Class isEditable(boolean isXpath, List<String> hierarchy, List<String> parts) {
Object obj = editable_prop_map;
for (int i = 0; i < parts.size(); i++) {
String part = parts.get(i);
boolean isAttr = false;
try {
isAttr = isXpath && part.startsWith("@");
} catch (RuntimeException e) {
throw e;
}
if (isAttr) {
part = part.substring(1);
}
if (hierarchy != null) hierarchy.add(part);
if (obj == null) return null;
if (i == parts.size() - 1) {
if (obj instanceof Map) {
Map map = (Map) obj;
Object o = map.get(part);
return checkType(o, isXpath, isAttr);
}
return null;
}
obj = ((Map) obj).get(part);
}
return null;
}
@SuppressWarnings({"rawtypes"})
static Class[] types = new Class[]{String.class, Boolean.class, Integer.class, Float.class};
@SuppressWarnings({"rawtypes"})
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;
}
}
@SuppressWarnings({"unchecked"})
public Map<String, String> getEditableSubProperties(String xpath) {
Object o = Utils.getObjectByPath(props, false, StrUtils.splitSmart(xpath, '/'));
if (o instanceof Map) {
return (Map<String,String>) o;
} else {
return null;
}
}
public Map<String, Object> getUserProps() {
return userProps;
}
@Override
public Map<String, Object> toMap(Map<String, Object> map) {
map.put(ZNODEVER, znodeVersion);
map.putAll(data);
return map;
}
@SuppressWarnings({"unchecked", "rawtypes"})
public Map<String, Map> getNamedPlugins(String typ) {
Map<String, Map> reqHandlers = (Map<String, Map>) data.get(typ);
if (reqHandlers == null) return Collections.EMPTY_MAP;
return Collections.unmodifiableMap(reqHandlers);
}
boolean hasKey(String key) {
return props.containsKey(key);
}
@SuppressWarnings({"unchecked", "rawtypes"})
public ConfigOverlay addNamedPlugin(Map<String, Object> info, String typ) {
Map dataCopy = Utils.getDeepCopy(data, 4);
Map existing = (Map) dataCopy.get(typ);
if (existing == null) dataCopy.put(typ, existing = new LinkedHashMap());
existing.put(info.get(CoreAdminParams.NAME), info);
return new ConfigOverlay(dataCopy, this.znodeVersion);
}
@SuppressWarnings({"unchecked", "rawtypes"})
public ConfigOverlay deleteNamedPlugin(String name, String typ) {
Map dataCopy = Utils.getDeepCopy(data, 4);
Map reqHandler = (Map) dataCopy.get(typ);
if (reqHandler == null) return this;
reqHandler.remove(name);
return new ConfigOverlay(dataCopy, this.znodeVersion);
}
public static final String ZNODEVER = "znodeVersion";
public static final String NAME = "overlay";
}