package org.zstack.core.config;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.zstack.core.Platform;
import org.zstack.core.cloudbus.EventCallback;
import org.zstack.core.cloudbus.EventFacade;
import org.zstack.core.config.GlobalConfigCanonicalEvents.UpdateEvent;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.db.SimpleQuery.Op;
import org.zstack.utils.CollectionUtils;
import org.zstack.utils.TypeUtils;
import org.zstack.utils.Utils;
import org.zstack.utils.function.ForEachFunction;
import org.zstack.utils.gson.JSONObjectUtil;
import org.zstack.utils.logging.CLogger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.zstack.utils.CollectionDSL.e;
import static org.zstack.utils.CollectionDSL.map;
import static org.zstack.utils.StringDSL.s;
/**
*/
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class GlobalConfig {
private static final CLogger logger = Utils.getLogger(GlobalConfig.class);
private String name;
private String category;
private String description;
private String type;
private String validatorRegularExpression;
private String defaultValue;
private volatile String value;
private boolean linked;
private transient List<GlobalConfigUpdateExtensionPoint> updateExtensions = new ArrayList<GlobalConfigUpdateExtensionPoint>();
private transient List<GlobalConfigValidatorExtensionPoint> validators = new ArrayList<GlobalConfigValidatorExtensionPoint>();
private transient List<GlobalConfigUpdateExtensionPoint> localUpdateExtensions = new ArrayList<GlobalConfigUpdateExtensionPoint>();
private GlobalConfigDef configDef;
@Autowired
private DatabaseFacade dbf;
@Autowired
private EventFacade evtf;
@Override
public String toString() {
return JSONObjectUtil.toJsonString(map(
e("name", name),
e("category", category),
e("type", type),
e("description", description),
e("defaultValue", defaultValue),
e("value", value),
e("validatorRegularExpression", validatorRegularExpression)
));
}
public GlobalConfig(String category, String name) {
this.category = category;
this.name = name;
}
GlobalConfig() {
}
private String makeUpdateEventPath() {
return s(GlobalConfigCanonicalEvents.UPDATE_EVENT_PATH).formatByMap(map(
e("nodeUuid", Platform.getManagementServerId()),
e("category", category),
e("name", name)
));
}
public GlobalConfigVO reload() {
SimpleQuery<GlobalConfigVO> q = dbf.createQuery(GlobalConfigVO.class);
q.add(GlobalConfigVO_.category, Op.EQ, category);
q.add(GlobalConfigVO_.name, Op.EQ, name);
return q.find();
}
public void installLocalUpdateExtension(GlobalConfigUpdateExtensionPoint ext) {
localUpdateExtensions.add(ext);
}
public void installUpdateExtension(GlobalConfigUpdateExtensionPoint ext) {
updateExtensions.add(ext);
}
public void installValidateExtension(GlobalConfigValidatorExtensionPoint ext) {
validators.add(ext);
}
public String getName() {
return name;
}
void setName(String name) {
this.name = name;
}
public String getCategory() {
return category;
}
void setCategory(String category) {
this.category = category;
}
public String getDescription() {
return description;
}
void setDescription(String description) {
this.description = description;
}
public String getType() {
return type;
}
void setType(String type) {
this.type = type;
}
public String getValidatorRegularExpression() {
return validatorRegularExpression;
}
void setValidatorRegularExpression(String validatorRegularExpression) {
this.validatorRegularExpression = validatorRegularExpression;
}
public String getDefaultValue() {
return defaultValue;
}
void setDefaultValue(String defaultValue) {
this.defaultValue = defaultValue;
}
public String value() {
return value;
}
void setValue(String value) {
this.value = value;
}
public <T> T value(Class<T> clz) {
return TypeUtils.stringToValue(value, clz);
}
public static GlobalConfig valueOf(GlobalConfigVO vo) {
GlobalConfig conf = new GlobalConfig();
conf.setName(vo.getName());
conf.setCategory(vo.getCategory());
conf.setDefaultValue(vo.getDefaultValue());
conf.setDescription(vo.getDescription());
conf.setValue(vo.getValue());
return conf;
}
public static GlobalConfig valueOf(GlobalConfig old) {
GlobalConfig ng = new GlobalConfig();
ng.setName(old.getName());
ng.setValue(old.value());
ng.setCategory(old.getCategory());
ng.setDescription(old.getDescription());
ng.setDefaultValue(ng.getDefaultValue());
ng.setValidatorRegularExpression(old.getValidatorRegularExpression());
return ng;
}
public GlobalConfigVO toVO() {
GlobalConfigVO vo = new GlobalConfigVO();
vo.setCategory(category);
vo.setValue(value);
vo.setDescription(description);
vo.setDefaultValue(defaultValue);
vo.setName(name);
return vo;
}
public static GlobalConfig valueOf(org.zstack.core.config.schema.GlobalConfig.Config c) {
GlobalConfig conf = new GlobalConfig();
conf.setName(c.getName());
conf.setCategory(c.getCategory());
conf.setDefaultValue(c.getDefaultValue());
conf.setDescription(c.getDescription());
conf.setValue(c.getValue());
conf.setType(c.getType());
return conf;
}
public String getIdentity() {
return produceIdentity(category, name);
}
public static String produceIdentity(String category, String name) {
return String.format("%s.%s", category, name);
}
void validate() {
validate(value);
}
private void validate(String newValue) {
for (GlobalConfigValidatorExtensionPoint ext : validators) {
ext.validateGlobalConfig(category, name, value, newValue);
}
}
void init() {
evtf.on(s(GlobalConfigCanonicalEvents.UPDATE_EVENT_PATH).formatByMap(map(
e("category", category),
e("name", name)
)), new EventCallback() {
@Override
public void run(Map tokens, Object data) {
String nodeUuid = (String) tokens.get("nodeUuid");
if (Platform.getManagementServerId().equals(nodeUuid)) {
return;
}
UpdateEvent evt = (UpdateEvent)data;
update(evt.getNewValue(), false);
logger.info(String.format("GlobalConfig[category: %s, name: %s] was updated in other management node[uuid:%s]," +
"in line with that change, updated ours. %s --> %s", category, name, nodeUuid, evt.getOldValue(), value));
}
});
}
private void update(String newValue, boolean localUpdate) {
validate(newValue);
SimpleQuery<GlobalConfigVO> q = dbf.createQuery(GlobalConfigVO.class);
q.add(GlobalConfigVO_.category, Op.EQ, category);
q.add(GlobalConfigVO_.name, Op.EQ, name);
GlobalConfigVO vo = q.find();
final GlobalConfig origin = valueOf(vo);
value = newValue;
if (localUpdate) {
vo.setValue(newValue);
dbf.update(vo);
final GlobalConfig self = this;
CollectionUtils.safeForEach(localUpdateExtensions, new ForEachFunction<GlobalConfigUpdateExtensionPoint>() {
@Override
public void run(GlobalConfigUpdateExtensionPoint ext) {
ext.updateGlobalConfig(origin, self);
}
});
}
for (GlobalConfigUpdateExtensionPoint ext : updateExtensions) {
try {
ext.updateGlobalConfig(origin, this);
} catch (Throwable t) {
logger.warn(String.format("unhandled exception when calling %s", ext.getClass()));
}
}
if (localUpdate) {
UpdateEvent evt = new UpdateEvent();
evt.setOldValue(origin.value());
evt.setNewValue(newValue);
evtf.fire(makeUpdateEventPath(), evt);
}
logger.debug(String.format("updated global config[category:%s, name:%s]: %s to %s", category, name, origin.value(), value));
}
public void updateValue(Object val) {
if (TypeUtils.nullSafeEquals(value, val)) {
return;
}
String newValue = val == null ? null : val.toString();
update(newValue, true);
}
boolean isLinked() {
return linked;
}
void setLinked(boolean linked) {
this.linked = linked;
}
public boolean isMe(GlobalConfig other) {
return category.equals(other.getCategory()) && name.equals(other.getName());
}
public GlobalConfigDef getConfigDef() {
return configDef;
}
public void setConfigDef(GlobalConfigDef configDef) {
this.configDef = configDef;
}
public String getCanonicalName() {
return String.format("Global config[category: %s, name: %s]", category, name);
}
}