package org.zstack.core;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.LocaleUtils;
import org.apache.commons.lang.StringUtils;
import org.reflections.Reflections;
import org.reflections.scanners.*;
import org.reflections.util.ClasspathHelper;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.web.context.WebApplicationContext;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.componentloader.ComponentLoader;
import org.zstack.core.componentloader.ComponentLoaderImpl;
import org.zstack.core.config.GlobalConfigFacade;
import org.zstack.core.db.DatabaseGlobalProperty;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.core.statemachine.StateMachine;
import org.zstack.core.statemachine.StateMachineImpl;
import org.zstack.header.Component;
import org.zstack.header.core.encrypt.ENCRYPT;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.errorcode.SysErrors;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.utils.*;
import org.zstack.utils.data.StringTemplate;
import org.zstack.utils.logging.CLogger;
import org.zstack.utils.logging.CLoggerImpl;
import org.zstack.utils.network.NetworkUtils;
import org.zstack.utils.path.PathUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static org.zstack.utils.CollectionDSL.e;
import static org.zstack.utils.CollectionDSL.map;
import static org.zstack.utils.StringDSL.ln;
public class Platform {
private static final CLogger logger = CLoggerImpl.getLogger(Platform.class);
private static ComponentLoader loader;
private static String msId;
private static String codeVersion;
private static String managementServerIp;
private static String managementCidr;
private static MessageSource messageSource;
public static final String COMPONENT_CLASSPATH_HOME = "componentsHome";
public static final String FAKE_UUID = "THIS_IS_IS_A_FAKE_UUID";
private static final Map<String, String> globalProperties = new HashMap<String, String>();
private static Locale locale;
public static volatile boolean IS_RUNNING = true;
private static Reflections reflections;
public static Reflections getReflections() {
return reflections;
}
public static Set<Method> encryptedMethodsMap;
private static Map<String, String> linkGlobalPropertyMap(String prefix) {
Map<String, String> ret = new HashMap<String, String>();
Map<String, String> map = getGlobalPropertiesStartWith(prefix);
if (map.isEmpty()) {
return ret;
}
for (Map.Entry<String, String> e : map.entrySet()) {
String key = StringDSL.stripStart(e.getKey(), prefix).trim();
ret.put(key, e.getValue().trim());
}
return ret;
}
private static void linkGlobalProperty(Class clz, Map<String, String> propertiesMap) {
for (Field f : clz.getDeclaredFields()) {
GlobalProperty at = f.getAnnotation(GlobalProperty.class);
if (at == null) {
continue;
}
if (!Modifier.isStatic(f.getModifiers())) {
throw new CloudRuntimeException(String.format("%s.%s is annotated by @GlobalProperty but it's not defined with static modifier", clz.getName(), f.getName()));
}
Object valueToSet = null;
String name = at.name();
if (Map.class.isAssignableFrom(f.getType())) {
Map ret = linkGlobalPropertyMap(name);
if (ret.isEmpty() && at.required()) {
throw new IllegalArgumentException(String.format("A required global property[%s] missing in zstack.properties", name));
}
valueToSet = ret;
} else if (List.class.isAssignableFrom(f.getType())) {
List ret = linkGlobalPropertyList(name);
if (ret.isEmpty() && at.required()) {
throw new IllegalArgumentException(String.format("A required global property[%s] missing in zstack.properties", name));
}
valueToSet = ret;
} else {
String value = propertiesMap.get(name);
if (value == null && at.defaultValue().equals(GlobalProperty.DEFAULT_NULL_STRING) && at.required()) {
throw new IllegalArgumentException(String.format("A required global property[%s] missing in zstack.properties", name));
}
if (value == null) {
value = at.defaultValue();
}
if (GlobalProperty.DEFAULT_NULL_STRING.equals(value)) {
value = null;
}
if (value != null) {
value = StringTemplate.substitute(value, propertiesMap);
}
if (Integer.class.isAssignableFrom(f.getType()) || Integer.TYPE.isAssignableFrom(f.getType())) {
valueToSet = TypeUtils.stringToValue(value, Integer.class, 0);
} else if (Long.class.isAssignableFrom(f.getType()) || Long.TYPE.isAssignableFrom(f.getType())) {
valueToSet = TypeUtils.stringToValue(value, Long.class, 0L);
} else if (Float.class.isAssignableFrom(f.getType()) || Float.TYPE.isAssignableFrom(f.getType())) {
valueToSet = TypeUtils.stringToValue(value, Float.class, 0F);
} else if (Double.class.isAssignableFrom(f.getType()) || Double.TYPE.isAssignableFrom(f.getType())) {
valueToSet = TypeUtils.stringToValue(value, Double.class, 0D);
} else if (String.class.isAssignableFrom(f.getType())) {
valueToSet = value;
} else if (Boolean.class.isAssignableFrom(f.getType()) || Boolean.TYPE.isAssignableFrom(f.getType())) {
valueToSet = TypeUtils.stringToValue(value, Boolean.class);
} else {
throw new CloudRuntimeException(String.format("%s.%s of type[%s] is unsupported by global property. try use Platform.getGlobalProperty() and parse by yourself",
clz.getName(), f.getName(), f.getType().getName()));
}
}
f.setAccessible(true);
try {
f.set(null, valueToSet);
globalProperties.put(name, valueToSet == null ? "null" : valueToSet.toString());
logger.debug(String.format("linked global property[%s.%s], value: %s", clz.getName(), f.getName(), valueToSet));
} catch (IllegalAccessException e) {
throw new CloudRuntimeException(String.format("unable to link global property[%s.%s]", clz.getName(), f.getName()), e);
}
}
}
public static Map<String, String> getGlobalProperties() {
return globalProperties;
}
private static List linkGlobalPropertyList(String name) {
Map<String, String> map = getGlobalPropertiesStartWith(name);
List<String> ret = new ArrayList<String>(map.size());
if (map.isEmpty()) {
return ret;
}
List<String> orderedKeys = new ArrayList<String>();
orderedKeys.addAll(map.keySet());
Collections.sort(orderedKeys);
for (String key : orderedKeys) {
String index = StringDSL.stripStart(key, name).trim();
try {
Long.valueOf(index);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(String.format("[Illegal List Definition] %s is an invalid list key" +
" definition, the last character must be a number, for example %s1. %s is not a number", key, key, index));
}
ret.add(map.get(key));
}
return ret;
}
private static void linkGlobalProperty() {
Set<Class<?>> clzs = reflections.getTypesAnnotatedWith(GlobalPropertyDefinition.class);
boolean noTrim = System.getProperty("DoNotTrimPropertyFile") != null;
List<String> lst = new ArrayList<String>();
Map<String, String> propertiesMap = new HashMap<String, String>();
for (final String name: System.getProperties().stringPropertyNames()) {
String value = System.getProperty(name);
if (!noTrim) {
value = value.trim();
}
propertiesMap.put(name, value);
lst.add(String.format("%s=%s", name, value));
}
logger.debug(String.format("system properties:\n%s", StringUtils.join(lst, ",")));
for (Class clz : clzs) {
linkGlobalProperty(clz, propertiesMap);
}
}
private static void writePidFile() throws IOException {
if (CoreGlobalProperty.UNIT_TEST_ON) {
return;
}
File pidFile = new File(CoreGlobalProperty.PID_FILE_PATH);
if (pidFile.exists()) {
String pidStr = FileUtils.readFileToString(pidFile);
try {
long pid = Long.valueOf(pidStr);
String processProcDir = String.format("/proc/%s", pid);
File processProcDirFile = new File(processProcDir);
if (processProcDirFile.exists()) {
throw new CloudRuntimeException(String.format("pid file[%s] exists and the process[pid:%s] that the pid file points to is still running", CoreGlobalProperty.PID_FILE_PATH, pidStr));
}
} catch (NumberFormatException e) {
logger.warn(String.format("pid file[%s] includes an invalid pid[%s] that is not a long number, ignore it",
CoreGlobalProperty.PID_FILE_PATH, pidStr));
}
logger.info(String.format("stale pid file[%s], ignore it", CoreGlobalProperty.PID_FILE_PATH));
}
pidFile.deleteOnExit();
String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
FileUtils.writeStringToFile(pidFile, pid);
}
private static void prepareDefaultDbProperties() {
if (DatabaseGlobalProperty.DbUrl != null) {
String dbUrl = DatabaseGlobalProperty.DbUrl;
if (dbUrl.endsWith("/")) {
dbUrl = dbUrl.substring(0, dbUrl.length()-1);
}
if (getGlobalProperty("DbFacadeDataSource.jdbcUrl") == null) {
String url;
if (dbUrl.contains("{database}")) {
url = ln(dbUrl).formatByMap(
map(e("database", "zstack"))
);
} else {
url = String.format("%s/zstack", dbUrl);
}
System.setProperty("DbFacadeDataSource.jdbcUrl", url);
logger.debug(String.format("default DbFacadeDataSource.jdbcUrl to DB.url [%s]", url));
}
if (getGlobalProperty("RESTApiDataSource.jdbcUrl") == null) {
String url;
if (dbUrl.contains("{database}")) {
url = ln(dbUrl).formatByMap(
map(e("database", "zstack_rest"))
);
} else {
url = String.format("%s/zstack_rest", dbUrl);
}
System.setProperty("RESTApiDataSource.jdbcUrl", url);
logger.debug(String.format("default RESTApiDataSource.jdbcUrl to DB.url [%s]", url));
}
}
if (DatabaseGlobalProperty.DbUser != null) {
if (getGlobalProperty("DbFacadeDataSource.user") == null) {
System.setProperty("DbFacadeDataSource.user", DatabaseGlobalProperty.DbUser);
logger.debug(String.format("default RESTApiDataSource.user to DB.user [%s]", DatabaseGlobalProperty.DbUser));
}
if (getGlobalProperty("RESTApiDataSource.user") == null) {
System.setProperty("RESTApiDataSource.user", DatabaseGlobalProperty.DbUser);
logger.debug(String.format("default RESTApiDataSource.user to DB.user [%s]", DatabaseGlobalProperty.DbUser));
}
}
if (DatabaseGlobalProperty.DbPassword != null) {
if (getGlobalProperty("DbFacadeDataSource.password") == null) {
System.setProperty("DbFacadeDataSource.password", DatabaseGlobalProperty.DbPassword);
logger.debug(String.format("default DbFacadeDataSource.password to DB.password [%s]", DatabaseGlobalProperty.DbPassword));
}
if (getGlobalProperty("RESTApiDataSource.password") == null) {
System.setProperty("RESTApiDataSource.password", DatabaseGlobalProperty.DbPassword);
logger.debug(String.format("default RESTApiDataSource.password to DB.password [%s]", DatabaseGlobalProperty.DbPassword));
}
}
if (DatabaseGlobalProperty.DbMaxIdleTime != null) {
if (getGlobalProperty("DbFacadeDataSource.maxIdleTime") == null) {
System.setProperty("DbFacadeDataSource.maxIdleTime", DatabaseGlobalProperty.DbMaxIdleTime);
logger.debug(String.format("default DbFacadeDataSource.maxIdleTime to DB.maxIdleTime [%s]", DatabaseGlobalProperty.DbMaxIdleTime));
}
if (getGlobalProperty("ExtraDataSource.maxIdleTime") == null) {
System.setProperty("ExtraDataSource.maxIdleTime", DatabaseGlobalProperty.DbMaxIdleTime);
logger.debug(String.format("default ExtraDataSource.maxIdleTime to DB.maxIdleTime [%s]", DatabaseGlobalProperty.DbMaxIdleTime));
}
if (getGlobalProperty("RESTApiDataSource.maxIdleTime") == null) {
System.setProperty("RESTApiDataSource.maxIdleTime", DatabaseGlobalProperty.DbMaxIdleTime);
logger.debug(String.format("default RESTApiDataSource.maxIdleTime to DB.maxIdleTime [%s]", DatabaseGlobalProperty.DbMaxIdleTime));
}
}
if (DatabaseGlobalProperty.DbIdleConnectionTestPeriod != null) {
if (getGlobalProperty("DbFacadeDataSource.idleConnectionTestPeriod") == null) {
System.setProperty("DbFacadeDataSource.idleConnectionTestPeriod", DatabaseGlobalProperty.DbIdleConnectionTestPeriod);
logger.debug(String.format("default DbFacadeDataSource.idleConnectionTestPeriod to DB.idleConnectionTestPeriod [%s]", DatabaseGlobalProperty.DbIdleConnectionTestPeriod));
}
if (getGlobalProperty("ExtraDataSource.idleConnectionTestPeriod") == null) {
System.setProperty("ExtraDataSource.idleConnectionTestPeriod", DatabaseGlobalProperty.DbIdleConnectionTestPeriod);
logger.debug(String.format("default ExtraDataSource.idleConnectionTestPeriod to DB.idleConnectionTestPeriod [%s]", DatabaseGlobalProperty.DbIdleConnectionTestPeriod));
}
if (getGlobalProperty("RESTApiDataSource.idleConnectionTestPeriod") == null) {
System.setProperty("RESTApiDataSource.idleConnectionTestPeriod", DatabaseGlobalProperty.DbIdleConnectionTestPeriod);
logger.debug(String.format("default RESTApiDataSource.idleConnectionTestPeriod to DB.idleConnectionTestPeriod [%s]", DatabaseGlobalProperty.DbIdleConnectionTestPeriod));
}
}
}
static {
try {
msId = getUuid();
reflections = new Reflections(ClasspathHelper.forPackage("org.zstack"),
new SubTypesScanner(), new MethodAnnotationsScanner(), new FieldAnnotationsScanner(),
new MemberUsageScanner(), new MethodParameterNamesScanner(), new ResourcesScanner(),
new TypeAnnotationsScanner(), new TypeElementsScanner(), new MethodParameterScanner());
// TODO: get code version from MANIFEST file
codeVersion = "0.1.0";
File globalPropertiesFile = PathUtil.findFileOnClassPath("zstack.properties", true);
FileInputStream in = new FileInputStream(globalPropertiesFile);
System.getProperties().load(in);
linkGlobalProperty();
prepareDefaultDbProperties();
callStaticInitMethods();
encryptedMethodsMap = getAllEncryptPassword();
writePidFile();
} catch (Throwable e) {
logger.warn(String.format("unhandled exception when in Platform's static block, %s", e.getMessage()), e);
new BootErrorLog().write(e.getMessage());
if (CoreGlobalProperty.EXIT_JVM_ON_BOOT_FAILURE) {
System.exit(1);
} else {
throw new RuntimeException(e);
}
}
}
private static Set<Method> getAllEncryptPassword() {
Set<Method> encrypteds = reflections.getMethodsAnnotatedWith(ENCRYPT.class);
for (Method encrypted: encrypteds) {
logger.debug(String.format("found encrypted method[%s:%s]", encrypted.getDeclaringClass(), encrypted.getName()));
}
return encrypteds;
}
private static void callStaticInitMethods() throws InvocationTargetException, IllegalAccessException {
Set<Method> inits = reflections.getMethodsAnnotatedWith(StaticInit.class);
for (Method init : inits) {
if (!Modifier.isStatic(init.getModifiers())) {
throw new CloudRuntimeException(String.format("the method[%s:%s] annotated by @StaticInit is not a static method", init.getDeclaringClass(), init.getName()));
}
logger.debug(String.format("calling static init method[%s:%s]", init.getDeclaringClass(), init.getName()));
init.setAccessible(true);
init.invoke(null);
}
}
private static void initMessageSource() {
locale = LocaleUtils.toLocale(CoreGlobalProperty.LOCALE);
logger.debug(String.format("using locale[%s] for i18n logging messages", locale.toString()));
if (loader == null) {
throw new CloudRuntimeException("ComponentLoader is null. i18n has not been initialized, you call it too early");
}
BeanFactory beanFactory = loader.getSpringIoc();
if (beanFactory == null) {
throw new CloudRuntimeException("BeanFactory is null. i18n has not been initialized, you call it too early");
}
if (!(beanFactory instanceof MessageSource)) {
throw new CloudRuntimeException("BeanFactory is not a spring MessageSource. i18n cannot be used");
}
messageSource = (MessageSource)beanFactory;
}
private static CloudBus bus;
{
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
if (bus != null) {
bus.stop();
}
}
}));
}
public static String getGlobalProperty(String name) {
return System.getProperty(name);
}
public static String getGlobalPropertyExceptionOnNull(String name) {
String ret = System.getProperty(name);
if (ret == null) {
throw new IllegalArgumentException(String.format("unable to find global properties[%s], check global.properties", name));
}
return ret;
}
public static Map<String, String> getGlobalPropertiesStartWith(String prefix) {
Properties props = System.getProperties();
Enumeration e = props.propertyNames();
Map<String, String> ret = new HashMap<String, String>();
while (e.hasMoreElements()) {
String key = (String) e.nextElement();
if (key.startsWith(prefix)) {
ret.put(key, System.getProperty(key));
}
}
return ret;
}
public static <T> T getGlobalProperty(String name, Class<T> clazz) {
String ret = System.getProperty(name);
return TypeUtils.stringToValue(ret, clazz);
}
public static <T> T getGlobalProperty(String name, Class<T> clazz, T defaultValue) {
String ret = System.getProperty(name);
if (ret == null) {
return defaultValue;
} else {
return TypeUtils.stringToValue(ret, clazz);
}
}
public static <T> T getGlobalPropertyExceptionOnNull(String name, Class<T> clazz) {
T ret = getGlobalProperty(name, clazz);
if (ret == null) {
throw new IllegalArgumentException(String.format("unable to find global properties[%s], check global.properties", name));
}
return ret;
}
public static ComponentLoader createComponentLoaderFromWebApplicationContext(WebApplicationContext webAppCtx) {
assert loader == null;
try {
if (webAppCtx != null) {
loader = new ComponentLoaderImpl(webAppCtx);
} else {
loader = new ComponentLoaderImpl();
}
} catch (Exception e) {
String err = "unable to create ComponentLoader";
logger.warn(e.getMessage(), e);
throw new CloudRuntimeException(err);
}
loader.getPluginRegistry();
GlobalConfigFacade gcf = loader.getComponent(GlobalConfigFacade.class);
if (gcf != null) {
((Component)gcf).start();
}
bus = loader.getComponentNoExceptionWhenNotExisting(CloudBus.class);
if (bus != null) {
bus.start();
}
initMessageSource();
return loader;
}
public static ComponentLoader getComponentLoader() {
/*
* This part cannot be moved to static block at the beginning.
* Because component code loaded by Spring may call other functions in Platform which
* causes the static block to be executed, which results in cycle initialization of ComponentLoaderImpl.
*/
if (loader == null) {
loader = createComponentLoaderFromWebApplicationContext(null);
}
return loader;
}
public static String getManagementServerId() {
return msId;
}
public static <K extends Enum<K>, T extends Enum<T>> StateMachine<K, T> createStateMachine() {
return new StateMachineImpl<K, T>();
}
public static String getCodeVersion() {
return codeVersion;
}
public static String getUuid() {
return UUID.randomUUID().toString().replace("-", "");
}
public static String getManagementCidr() {
if (managementCidr != null) {
return managementCidr;
}
String mgmtIp = getManagementServerIp();
managementCidr = ShellUtils.run(String.format("ip addr | grep -w %s | awk '{print $2}'", mgmtIp));
managementCidr = StringDSL.stripEnd(managementCidr, "\n");
if (!NetworkUtils.isCidr(managementCidr)) {
throw new CloudRuntimeException(String.format("got an invalid management CIDR[%s]", managementCidr));
}
return managementCidr;
}
public static String getManagementServerIp() {
if (managementServerIp != null) {
return managementServerIp;
}
String ip = System.getProperty("management.server.ip");
if (ip != null) {
logger.info(String.format("get management IP[%s] from Java property[management.server.ip]", ip));
return ip;
}
ip = System.getenv("ZSTACK_MANAGEMENT_SERVER_IP");
if (ip != null) {
logger.info(String.format("get management IP[%s] from environment variable[ZSTACK_MANAGEMENT_SERVER_IP]", ip));
return ip;
}
Linux.ShellResult ret = Linux.shell("/sbin/ip route");
String defaultLine = null;
for (String s : ret.getStdout().split("\n")) {
if (s.contains("default via")) {
defaultLine = s;
break;
}
}
String err = "cannot get management server ip of this machine. there are three ways to get the ip.\n1) search for 'management.server.ip' java property\n2) search for 'ZSTACK_MANAGEMENT_SERVER_IP' environment variable\n3) search for default route printed out by '/sbin/ip route'\nhowever, all above methods failed";
if (defaultLine == null) {
throw new CloudRuntimeException(err);
}
try {
Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
for (NetworkInterface iface : Collections.list(nets)) {
String name = iface.getName();
if (defaultLine.contains(name)) {
InetAddress ia = iface.getInetAddresses().nextElement();
ip = ia.getHostAddress();
break;
}
}
} catch (SocketException e) {
throw new CloudRuntimeException(e);
}
if (ip == null) {
throw new CloudRuntimeException(err);
}
logger.info(String.format("get management IP[%s] from default route[/sbin/ip route]", ip));
managementServerIp = ip;
return managementServerIp;
}
public static String toI18nString(String code, Object... args) {
return toI18nString(code, null, args);
}
public static String toI18nString(String code, Locale l, List args) {
return toI18nString(code, l, args.toArray(new Object[args.size()]));
}
public static String toI18nString(String code, Locale l, Object...args) {
l = l == null ? locale : l;
try {
String ret;
if (args.length > 0) {
ret = messageSource.getMessage(code, args, l);
} else {
ret = messageSource.getMessage(code, null, l);
}
// if the result is an empty string which means the string is not translated in the locale,
// return the original string so users won't get a confusing, empty string
return ret.isEmpty() ? String.format(code, args) : ret;
} catch (NoSuchMessageException e) {
return String.format(code, args);
}
}
public static String i18n(String str, Object...args) {
if (args != null) {
return String.format(str, args);
} else {
return str;
}
}
public static boolean killProcess(int pid) {
return killProcess(pid, 15);
}
public static boolean killProcess(int pid, Integer timeout) {
timeout = timeout == null ? 30 : timeout;
if (!TimeUtils.loopExecuteUntilTimeoutIgnoreExceptionAndReturn(timeout, 1, TimeUnit.SECONDS, () -> {
ShellUtils.runAndReturn(String.format("kill %s", pid));
return !new ProcessFinder().processExists(pid);
})) {
logger.warn(String.format("cannot kill the process[PID:%s] after %s seconds, kill -9 it", pid, timeout));
ShellUtils.runAndReturn(String.format("kill -9 %s", pid));
}
if (!TimeUtils.loopExecuteUntilTimeoutIgnoreExceptionAndReturn(5, 1, TimeUnit.SECONDS, () -> !new ProcessFinder().processExists(pid))) {
logger.warn(String.format("FAILED TO KILL -9 THE PROCESS[PID:%s], THE KERNEL MUST HAVE SOMETHING RUN", pid));
return false;
} else {
return true;
}
}
public static ErrorCode err(Enum errCode, String fmt, Object...args) {
ErrorFacade errf = getComponentLoader().getComponent(ErrorFacade.class);
if (SysErrors.INTERNAL == errCode) {
return errf.instantiateErrorCode(errCode, String.format(fmt, args));
} else {
return errf.instantiateErrorCode(errCode, i18n(fmt, args));
}
}
public static ErrorCode inerr(String fmt, Object...args) {
return err(SysErrors.INTERNAL, fmt, args);
}
public static ErrorCode operr(String fmt, Object...args) {
return err(SysErrors.OPERATION_ERROR, fmt, args);
}
public static ErrorCode argerr(String fmt, Object...args) {
return err(SysErrors.INVALID_ARGUMENT_ERROR, fmt, args);
}
public static ErrorCode ioerr(String fmt, Object...args) {
return err(SysErrors.IO_ERROR, fmt, args);
}
public static ErrorCode httperr(String fmt, Object...args) {
return err(SysErrors.HTTP_ERROR, fmt, args);
}
}