package org.zstack.core.timeout;
import org.reflections.Reflections;
import org.zstack.core.Platform;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.utils.*;
import org.zstack.utils.logging.CLogger;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* Created by frank on 2/17/2016.
*/
public class ApiTimeoutManagerImpl implements ApiTimeoutManager {
private static final CLogger logger = Utils.getLogger(ApiTimeoutManagerImpl.class);
private Map<Class, ApiTimeout> apiTimeouts = new HashMap<Class, ApiTimeout>();
private Map<Class, Long> timeouts = new HashMap<Class, Long>();
class Value {
private String valueString;
private String key;
private Map<String, String> values = new HashMap<String, String>();
public Value(String key, String valueString) {
this.valueString = valueString;
this.key = key;
String[] vals = valueString.split(",");
for (String val : vals) {
val = val.trim();
String[] keyval = val.split("::", 2);
if (keyval.length != 2) {
throw new IllegalArgumentException(String.format("the key/value pair must be in format of key::value;" +
"invalid configuration[%s=%s] found, check your zstack.properties", key, valueString));
}
values.put(keyval[0], keyval[1]);
}
}
public String getValue(String key) {
String val = values.get(key);
if (val == null) {
throw new IllegalArgumentException(String.format("not key[%s] found key/value pair[%s=%s], check your zstack.properties",
key, this.key, valueString));
}
return val;
}
}
private final String VALUE_TIMEOUT = "timeout";
void init() {
try {
collectTimeout();
collectTimeoutForDerivedApi();
flatTimeout();
} catch (RuntimeException e) {
new BootErrorLog().write(e.getMessage());
throw e;
}
}
private void flatTimeout() {
for (Map.Entry<Class, ApiTimeout> e :apiTimeouts.entrySet()) {
ApiTimeout v = e.getValue();
for (Class clz : v.getRelatives()) {
Long currentTimeout = timeouts.get(clz);
Long timeout = currentTimeout == null ? v.getTimeout() : Math.max(currentTimeout, v.getTimeout());
timeouts.put(clz, timeout);
}
}
}
private void collectTimeoutForDerivedApi() {
Reflections reflections = Platform.getReflections();
Map<Class, ApiTimeout> children = new HashMap<Class, ApiTimeout>();
for (Map.Entry<Class, ApiTimeout> e : apiTimeouts.entrySet()) {
Class clz = e.getKey();
ApiTimeout at = e.getValue();
Set<Class> subClasses = reflections.getSubTypesOf(clz);
for (Class child : subClasses) {
children.put(child, at);
if (logger.isTraceEnabled()) {
logger.trace(String.format("configure timeout for API[%s]:" +
"\nrelatives: %s" +
"\ntimeout: %sms", child, at.relatives, at.timeout));
}
}
}
apiTimeouts.putAll(children);
}
private void collectTimeout() {
Map<Class, Set<Class>> m = new HashMap<Class, Set<Class>>();
List<Class> subs = BeanUtils.scanClass("org.zstack", org.zstack.header.core.ApiTimeout.class);
for (Class sub : subs) {
org.zstack.header.core.ApiTimeout at = (org.zstack.header.core.ApiTimeout) sub.getAnnotation(org.zstack.header.core.ApiTimeout.class);
for (Class apiClz : at.apiClasses()) {
Set<Class> relatives = m.get(apiClz);
if (relatives == null) {
relatives = new HashSet<Class>();
m.put(apiClz, relatives);
}
relatives.add(sub);
}
}
for (final Map.Entry<String, String> e : Platform.getGlobalProperties().entrySet()) {
String key = e.getKey();
if (!key.startsWith("ApiTimeout.")) {
continue;
}
String apiName = StringDSL.stripStart(key, "ApiTimeout.").trim();
Class apiClz;
String ERROR_INFO = "The configuration must be in format of \n" +
" ApiTimeout.full_api_class_name = timeout::the_value_of_timeout(e.g. 1h, 30m)";
try {
apiClz = Class.forName(apiName);
} catch (ClassNotFoundException ex) {
throw new CloudRuntimeException(String.format("Invalid API timeout configuration[invalid key: %s], %s. %s",
key, ex.getMessage(), ERROR_INFO));
}
String value = e.getValue();
Value val = new Value(key, value);
long timeout = parseTimeout(val.getValue(VALUE_TIMEOUT));
ApiTimeout apiTimeout = new ApiTimeout();
apiTimeout.setTimeout(timeout);
apiTimeout.setRelatives(m.get(apiClz));
apiTimeouts.put(apiClz, apiTimeout);
if (logger.isTraceEnabled()) {
logger.trace(String.format("configure timeout for API[%s]:" +
"\nrelatives: %s" +
"\ntimeout: %sms", apiClz, apiTimeout.relatives, apiTimeout.timeout));
}
}
}
private long parseTimeout(String timeout) {
if ("5m".equals(timeout)) {
// optimization as the most of default timeout are 5 minutes
return 300000;
}
return TimeUtils.parseTimeInMillis(timeout);
}
@Override
public Long getTimeout(Class clz) {
return timeouts.get(clz);
}
@Override
public Long getTimeout(Class clz, long defaultTimeout) {
Long t = getTimeout(clz);
return t == null ? defaultTimeout : t;
}
@Override
public Long getTimeout(Class clz, String defaultTimeout) {
Long t = getTimeout(clz);
if (t == null) {
return parseTimeout(defaultTimeout);
} else {
return t;
}
}
@Override
public Long getTimeout(Class clz, TimeUnit tu) {
Long t = getTimeout(clz);
if (t != null) {
return tu.convert(t, TimeUnit.MILLISECONDS);
} else {
return null;
}
}
@Override
public Map<Class, ApiTimeout> getAllTimeout() {
return apiTimeouts;
}
}