package com.alibaba.dcm.agent;
import com.alibaba.dcm.DnsCache;
import com.alibaba.dcm.DnsCacheEntry;
import com.alibaba.dcm.DnsCacheManipulator;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Jerry Lee (oldratlee at gmail dot com)
* @since 1.4.0
*/
public class DcmAgent {
static final String FILE = "file";
static final String DCM_AGENT_SUCCESS_MARK_LINE = "!!DCM SUCCESS!!";
public static void agentmain(String agentArgument) throws Exception {
System.out.printf("%s: attached with agent argument: %s.\n", DcmAgent.class.getName(), agentArgument);
agentArgument = agentArgument.trim();
if (agentArgument.isEmpty()) {
System.out.println(DcmAgent.class.getName() + ": agent argument is blank, do nothing!");
return;
}
initAction2Method();
FileOutputStream fileOutputStream = null;
try {
final Map<String, List<String>> action2Arguments = parseAgentArgument(agentArgument);
PrintWriter filePrinter = null;
// Extract file argument, set file printer if needed
if (action2Arguments.containsKey(FILE)) {
fileOutputStream = new FileOutputStream(action2Arguments.get(FILE).get(0), false);
final OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "UTF-8");
filePrinter = new PrintWriter(outputStreamWriter, true);
action2Arguments.remove(FILE);
}
if (action2Arguments.isEmpty()) {
System.out.println(DcmAgent.class.getName() + ": No action in agent argument, do nothing!");
if (filePrinter != null) {
filePrinter.printf("No action in agent argument, do nothing! agent argument: %s.\n", agentArgument);
}
return;
}
boolean allSuccess = true;
for (Map.Entry<String, List<String>> entry : action2Arguments.entrySet()) {
final String action = entry.getKey();
final List<String> arguments = entry.getValue();
final String argumentString = join(arguments);
if (!action2Method.containsKey(action)) {
System.out.printf("%s: Unknown action %s, ignore! action: %<s %s!\n", DcmAgent.class.getName(), action, argumentString);
if (filePrinter != null) {
filePrinter.printf("Unknown action %s, ignore! action: %<s %s !\n", action, argumentString);
}
continue;
}
try {
final Object result = doAction(action, arguments.toArray(new String[0]));
printResult(action, result, filePrinter);
} catch (Exception e) {
allSuccess = false;
final String exString = throwable2StackString(e);
System.out.printf("%s: Error to do action %s %s, cause: %s\n", DcmAgent.class.getName(), action, argumentString, exString);
if (filePrinter != null) {
filePrinter.printf("Error to do action %s %s, cause: %s\n", action, argumentString, exString);
}
}
}
if (allSuccess && filePrinter != null) {
filePrinter.println(DCM_AGENT_SUCCESS_MARK_LINE);
}
} finally {
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (Throwable e) {
// do nothing!
}
}
}
}
static Map<String, List<String>> parseAgentArgument(String argument) {
final String[] split = argument.split("\\s+");
int idx = 0;
Map<String, List<String>> action2Arguments = new HashMap<String, List<String>>();
while (idx < split.length) {
final String action = split[idx++];
if (!action2Method.containsKey(action)) {
continue; // TODO error message
}
List<String> arguments = new ArrayList<String>();
while (idx < split.length) {
if (action2Method.containsKey(split[idx])) {
break;
}
arguments.add(split[idx++]);
}
action2Arguments.put(action, arguments);
}
return action2Arguments;
}
static String join(List<String> list) {
return join(list, " ");
}
static String join(List<String> list, String separator) {
StringBuilder ret = new StringBuilder();
for (String argument : list) {
if (ret.length() > 0) {
ret.append(separator);
}
ret.append(argument);
}
return ret.toString();
}
static Object doAction(String action, String[] arguments) throws Exception {
Method method = action2Method.get(action);
final Class<?>[] parameterTypes = method.getParameterTypes();
final Object[] methodArgs = convertStringArray2Arguments(action, arguments, parameterTypes);
return method.invoke(null, methodArgs);
}
static Object[] convertStringArray2Arguments(String action, String[] arguments, Class<?>[] parameterTypes) {
if (arguments.length < parameterTypes.length) {
final String message = String.format("action %s need more argument! arguments: %s", action, Arrays.toString(arguments));
throw new IllegalStateException(message);
}
if (parameterTypes.length == 0) {
return new Object[0];
}
final Object[] methodArgs = new Object[parameterTypes.length];
final int lastArgumentIdx = parameterTypes.length - 1;
if (parameterTypes[(lastArgumentIdx)] == String[].class) {
// set all tail method argument of type String[]
String[] varArgs = new String[arguments.length - lastArgumentIdx];
System.arraycopy(arguments, lastArgumentIdx, varArgs, 0, varArgs.length);
methodArgs[(lastArgumentIdx)] = varArgs;
} else if (arguments.length > parameterTypes.length) {
String message = String.format("Too more arguments for Action %s! arguments: %s", action, Arrays.toString(arguments));
throw new IllegalStateException(message);
}
for (int i = 0; i < parameterTypes.length; i++) {
if (methodArgs[i] != null) { // already set
continue;
}
Class<?> parameterType = parameterTypes[i];
final String argument = arguments[i];
if (parameterType.equals(String.class)) {
methodArgs[i] = argument;
} else if (parameterType.equals(int.class)) {
methodArgs[i] = Integer.parseInt(argument);
} else {
final String message = String.format("Unexpected method type %s! Bug!!", parameterType.getName());
throw new IllegalStateException(message);
}
}
return methodArgs;
}
static void printResult(String action, Object result, PrintWriter writer) {
if (writer == null) {
return;
}
final Method method = action2Method.get(action);
if (method.getReturnType() == void.class) {
return;
}
if (result == null) {
writer.println((Object) null);
} else if (result instanceof DnsCacheEntry) {
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
DnsCacheEntry entry = (DnsCacheEntry) result;
writer.printf("%s %s %s\n", entry.getHost(), join(Arrays.asList(entry.getIps()), ","), dateFormat.format(entry.getExpiration()));
} else if (result instanceof DnsCache) {
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
DnsCache dnsCache = (DnsCache) result;
writer.println("Dns cache:");
for (DnsCacheEntry entry : dnsCache.getCache()) {
writer.printf(" %s %s %s\n", entry.getHost(), join(Arrays.asList(entry.getIps()), ","), dateFormat.format(entry.getExpiration()));
}
writer.println("Dns negative cache: ");
for (DnsCacheEntry entry : dnsCache.getNegativeCache()) {
writer.printf(" %s %s %s\n", entry.getHost(), join(Arrays.asList(entry.getIps()), ","), dateFormat.format(entry.getExpiration()));
}
} else {
writer.println(result.toString());
}
}
static String throwable2StackString(Throwable e) {
final StringWriter w = new StringWriter();
e.printStackTrace(new PrintWriter(w, true));
return w.toString();
}
static volatile Map<String, Method> action2Method;
static synchronized void initAction2Method() throws Exception {
if (action2Method != null) return;
Map<String, Method> map = new HashMap<String, Method>();
map.put("set", DnsCacheManipulator.class.getMethod("setDnsCache", String.class, String[].class));
map.put("get", DnsCacheManipulator.class.getMethod("getDnsCache", String.class));
map.put("rm", DnsCacheManipulator.class.getMethod("removeDnsCache", String.class));
map.put("list", DnsCacheManipulator.class.getMethod("getWholeDnsCache"));
map.put("clear", DnsCacheManipulator.class.getMethod("clearDnsCache"));
map.put("setPolicy", DnsCacheManipulator.class.getMethod("setDnsCachePolicy", int.class));
map.put("getPolicy", DnsCacheManipulator.class.getMethod("getDnsCachePolicy"));
map.put("setNegativePolicy", DnsCacheManipulator.class.getMethod("setDnsNegativeCachePolicy", int.class));
map.put("getNegativePolicy", DnsCacheManipulator.class.getMethod("getDnsNegativeCachePolicy"));
map.put(FILE, null); // FAKE KEY
action2Method = map;
}
}