From 389380dda18f459ee41834eab050986f1b764ae4 Mon Sep 17 00:00:00 2001 From: pengzhile Date: Thu, 20 Jan 2022 14:55:03 +0800 Subject: [PATCH] 1. new feature: log to file 2. new feature: attach mode Signed-off-by: pengzhile --- README.md | 16 +- pom.xml | 12 +- .../java/com/janetfilter/core/Dispatcher.java | 2 +- .../com/janetfilter/core/Environment.java | 26 ++- .../com/janetfilter/core/Initializer.java | 5 +- .../java/com/janetfilter/core/Launcher.java | 63 ++++---- .../janetfilter/core/attach/VMDescriptor.java | 51 ++++++ .../janetfilter/core/attach/VMLauncher.java | 81 ++++++++++ .../janetfilter/core/attach/VMSelector.java | 124 ++++++++++++++ .../core/commons/ConfigParser.java | 4 +- .../janetfilter/core/commons/DebugInfo.java | 152 +++++++++++++++++- .../core/plugin/PluginManager.java | 8 +- .../com/janetfilter/core/utils/DateUtils.java | 12 +- .../janetfilter/core/utils/ProcessUtils.java | 75 +++++++++ .../janetfilter/core/utils/WhereIsUtils.java | 82 ++++++++++ ...daf08f4ba46dfde71b7f0624b0ad7f234a67a.txt} | 0 16 files changed, 650 insertions(+), 63 deletions(-) create mode 100644 src/main/java/com/janetfilter/core/attach/VMDescriptor.java create mode 100644 src/main/java/com/janetfilter/core/attach/VMLauncher.java create mode 100644 src/main/java/com/janetfilter/core/attach/VMSelector.java create mode 100644 src/main/java/com/janetfilter/core/utils/ProcessUtils.java create mode 100644 src/main/java/com/janetfilter/core/utils/WhereIsUtils.java rename src/main/resources/{4cc9c353c626d6510ca855ab6907ed7f64400257.txt => 288daf08f4ba46dfde71b7f0624b0ad7f234a67a.txt} (100%) diff --git a/README.md b/README.md index 41c4f23..0e1999a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ja-netfilter v2.1.1 +# ja-netfilter v2.2.0 ### A javaagent framework @@ -9,12 +9,13 @@ * add as an argument of the `java` command. eg: `java -javaagent:/absolute/path/to/ja-netfilter.jar -jar executable_jar_file.jar` * some apps support the `JVM Options file`, you can add as a line of the `JVM Options file`. * **WARNING: DO NOT put some unnecessary whitespace characters!** +* or execute `java -jar /path/to/ja-netfilter.jar` to use `attach mode`. * edit your plugin config files: `${lower plugin name}.conf` file in the `config` dir where `ja-netfilter.jar` is located. -* the `config` and `plugins` directory can be specified through **the javaagent args**. - * eg: `-javaagent:/path/to/ja-netfilter.jar=appName`, your config and plugins directories will be `config-appname` and `plugins-appname`. - * if no javaagent args, they default to `config` and `plugins`. - * this mechanism will avoid extraneous and bloated `config` and `plugins`. +* the `config`, `logs` and `plugins` directories can be specified through **the javaagent args**. + * eg: `-javaagent:/path/to/ja-netfilter.jar=appName`, your config, logs and plugins directories will be `config-appname`, `logs-appname` and `plugins-appname`. + * if no javaagent args, they default to `config`, `logs` and `plugins`. + * this mechanism will avoid extraneous and bloated `config`, `logs` and `plugins`. * run your java application and enjoy~ @@ -46,8 +47,9 @@ EQUAL,somedomain ## Debug info * the `ja-netfilter` will **NOT** output debugging information by default -* add environment variable `JANF_DEBUG=1` and start to enable it -* or add system property `-Djanf.debug=1` to enable it +* add environment variable `JANF_DEBUG=1` (log level) and start to enable it +* or add system property `-Djanf.debug=1` (log level) to enable it +* log level: `NONE=0`, `DEBUG=1`, `INFO=2`, `WARN=3`, `ERROR=4`; ## Plugin system diff --git a/pom.xml b/pom.xml index 956d137..74973f9 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.ja-netfilter ja-netfilter - 2.1.1 + 2.2.0 ja-netfilter A javaagent framework @@ -82,6 +82,7 @@ neo com.janetfilter.core.Launcher + com.janetfilter.core.Launcher com.janetfilter.core.Launcher true true @@ -163,4 +164,13 @@ + + + com.sun + tools + 1.8 + system + ${java.home}/../lib/tools.jar + + diff --git a/src/main/java/com/janetfilter/core/Dispatcher.java b/src/main/java/com/janetfilter/core/Dispatcher.java index 2d44b23..cd36e29 100644 --- a/src/main/java/com/janetfilter/core/Dispatcher.java +++ b/src/main/java/com/janetfilter/core/Dispatcher.java @@ -70,7 +70,7 @@ public final class Dispatcher implements ClassFileTransformer { classFileBuffer = transformer.transform(className, classFileBuffer, order++); } } catch (Throwable e) { - DebugInfo.output("Transform class failed: " + className, e); + DebugInfo.error("Transform class failed: " + className, e); } } while (false); diff --git a/src/main/java/com/janetfilter/core/Environment.java b/src/main/java/com/janetfilter/core/Environment.java index 0d873f0..5983972 100644 --- a/src/main/java/com/janetfilter/core/Environment.java +++ b/src/main/java/com/janetfilter/core/Environment.java @@ -1,14 +1,17 @@ package com.janetfilter.core; +import com.janetfilter.core.utils.ProcessUtils; import com.janetfilter.core.utils.StringUtils; import java.io.File; public final class Environment { + private final String pid; private final File baseDir; private final File agentFile; private final File configDir; private final File pluginsDir; + private final File logsDir; private final String nativePrefix; public Environment(File agentFile) { @@ -22,13 +25,20 @@ public final class Environment { if (StringUtils.isEmpty(app)) { configDir = new File(baseDir, "config"); pluginsDir = new File(baseDir, "plugins"); + logsDir = new File(baseDir, "logs"); } else { app = app.toLowerCase(); configDir = new File(baseDir, "config-" + app); pluginsDir = new File(baseDir, "plugins-" + app); + logsDir = new File(baseDir, "logs-" + app); } nativePrefix = StringUtils.randomMethodName(15) + "_"; + pid = ProcessUtils.currentId(); + } + + public String getPid() { + return pid; } public File getBaseDir() { @@ -47,6 +57,10 @@ public final class Environment { return pluginsDir; } + public File getLogsDir() { + return logsDir; + } + public String getNativePrefix() { return nativePrefix; } @@ -54,11 +68,13 @@ public final class Environment { @Override public String toString() { return "Environment: {" + - "\n\tbaseDir=" + baseDir + - ", \n\tagentFile=" + agentFile + - ", \n\tconfigDir=" + configDir + - ", \n\tpluginsDir=" + pluginsDir + - ", \n\tnativePrefix=" + nativePrefix + + "\n\tpid = " + pid + + ", \n\tbaseDir = " + baseDir + + ", \n\tagentFile = " + agentFile + + ", \n\tconfigDir = " + configDir + + ", \n\tpluginsDir = " + pluginsDir + + ", \n\tlogsDir = " + logsDir + + ", \n\tnativePrefix = " + nativePrefix + "\n}"; } } diff --git a/src/main/java/com/janetfilter/core/Initializer.java b/src/main/java/com/janetfilter/core/Initializer.java index 9d609cc..7634f5c 100644 --- a/src/main/java/com/janetfilter/core/Initializer.java +++ b/src/main/java/com/janetfilter/core/Initializer.java @@ -8,7 +8,8 @@ import java.util.Set; public class Initializer { public static void init(Instrumentation inst, Environment environment) { - DebugInfo.output(environment.toString()); + DebugInfo.useFile(environment.getLogsDir()); + DebugInfo.info(environment.toString()); Dispatcher dispatcher = new Dispatcher(); new PluginManager(inst, dispatcher, environment).loadPlugins(); @@ -26,7 +27,7 @@ public class Initializer { try { inst.retransformClasses(c); } catch (Throwable e) { - DebugInfo.output("Retransform class failed: " + name, e); + DebugInfo.error("Retransform class failed: " + name, e); } } } diff --git a/src/main/java/com/janetfilter/core/Launcher.java b/src/main/java/com/janetfilter/core/Launcher.java index e0cd5a1..9893400 100644 --- a/src/main/java/com/janetfilter/core/Launcher.java +++ b/src/main/java/com/janetfilter/core/Launcher.java @@ -1,25 +1,49 @@ package com.janetfilter.core; +import com.janetfilter.core.attach.VMLauncher; +import com.janetfilter.core.attach.VMSelector; import com.janetfilter.core.commons.DebugInfo; +import com.janetfilter.core.utils.WhereIsUtils; import java.io.File; import java.lang.instrument.Instrumentation; import java.net.URI; -import java.net.URL; import java.util.jar.JarFile; public class Launcher { - private static final String VERSION = "v2.1.1"; + public static final String ATTACH_ARG = "--attach"; + public static final String VERSION = "v2.2.0"; private static boolean loaded = false; public static void main(String[] args) { + URI jarURI; + try { + jarURI = WhereIsUtils.getJarURI(); + } catch (Throwable e) { + DebugInfo.error("Can not locate `ja-netfilter` jar file.", e); + return; + } + + String jarPath = jarURI.getPath(); + if (args.length > 1 && args[0].equals(ATTACH_ARG)) { + VMLauncher.attachVM(jarPath, args[1], args.length > 2 ? args[2] : null); + return; + } + printUsage(); + + try { + new VMSelector(new File(jarPath)).select(); + } catch (Throwable e) { + System.err.println(" ERROR: Select virtual machine failed."); + e.printStackTrace(System.err); + } } public static void premain(String args, Instrumentation inst) { if (loaded) { - DebugInfo.output("WARN: You have multiple `ja-netfilter` as -javaagent."); + DebugInfo.warn("You have multiple `ja-netfilter` as javaagent."); return; } @@ -28,9 +52,9 @@ public class Launcher { URI jarURI; try { loaded = true; - jarURI = getJarURI(); + jarURI = WhereIsUtils.getJarURI(); } catch (Throwable e) { - DebugInfo.output("ERROR: Can not locate ja-netfilter jar file.", e); + DebugInfo.error("Can not locate `ja-netfilter` jar file.", e); return; } @@ -38,13 +62,17 @@ public class Launcher { try { inst.appendToBootstrapClassLoaderSearch(new JarFile(agentFile)); } catch (Throwable e) { - DebugInfo.output("ERROR: Can not access ja-netfilter jar file.", e); + DebugInfo.error("Can not access `ja-netfilter` jar file.", e); return; } Initializer.init(inst, new Environment(agentFile, args)); // for some custom UrlLoaders } + public static void agentmain(String args, Instrumentation inst) { + premain(args, inst); + } + private static void printUsage() { String content = "\n ============================================================================ \n" + "\n" + @@ -57,28 +85,5 @@ public class Launcher { " ============================================================================ \n\n"; System.out.print(content); - System.out.flush(); - } - - private static URI getJarURI() throws Exception { - URL url = Launcher.class.getProtectionDomain().getCodeSource().getLocation(); - if (null != url) { - return url.toURI(); - } - - String resourcePath = "/4cc9c353c626d6510ca855ab6907ed7f64400257.txt"; - url = Launcher.class.getResource(resourcePath); - if (null == url) { - throw new Exception("Can not locate resource file."); - } - - String path = url.getPath(); - if (!path.endsWith("!" + resourcePath)) { - throw new Exception("Invalid resource path."); - } - - path = path.substring(0, path.length() - resourcePath.length() - 1); - - return new URI(path); } } diff --git a/src/main/java/com/janetfilter/core/attach/VMDescriptor.java b/src/main/java/com/janetfilter/core/attach/VMDescriptor.java new file mode 100644 index 0000000..52fff73 --- /dev/null +++ b/src/main/java/com/janetfilter/core/attach/VMDescriptor.java @@ -0,0 +1,51 @@ +package com.janetfilter.core.attach; + +public class VMDescriptor { + private String id; + private String className; + private String args; + private Boolean old = true; + + public VMDescriptor(String id, String className, String args) { + this.id = id; + this.className = className; + this.args = args; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getClassName() { + return className; + } + + public void setClassName(String className) { + this.className = className; + } + + public String getArgs() { + return args; + } + + public void setArgs(String args) { + this.args = args; + } + + public Boolean getOld() { + return old; + } + + public void setOld(Boolean old) { + this.old = old; + } + + @Override + public String toString() { + return id + " " + className; + } +} diff --git a/src/main/java/com/janetfilter/core/attach/VMLauncher.java b/src/main/java/com/janetfilter/core/attach/VMLauncher.java new file mode 100644 index 0000000..f5aacaf --- /dev/null +++ b/src/main/java/com/janetfilter/core/attach/VMLauncher.java @@ -0,0 +1,81 @@ +package com.janetfilter.core.attach; + +import com.janetfilter.core.Launcher; +import com.janetfilter.core.utils.ProcessUtils; +import com.janetfilter.core.utils.WhereIsUtils; +import com.sun.tools.attach.VirtualMachine; + +import java.io.File; +import java.io.IOException; + +public class VMLauncher { + public static void attachVM(String agentFile, String pid, String args) { + try { + VirtualMachine vm = VirtualMachine.attach(pid); + vm.loadAgent(agentFile, args); + vm.detach(); + } catch (IOException e) { + if (e.getMessage().startsWith("Non-numeric value found")) { + System.out.println("WARN: The jdk used by `ja-netfilter` does not match the attached jdk version"); + } + } catch (Throwable e) { + System.err.println("Attach failed: " + pid); + e.printStackTrace(System.err); + return; + } + + System.out.println("ATTACHED SUCCESSFULLY: " + pid); + } + + public static void launch(File thisJar, VMDescriptor descriptor, String args) throws Exception { + File javaCommand = WhereIsUtils.findJava(); + if (null == javaCommand) { + throw new Exception("Can not locate java command, unable to start attach mode."); + } + + ProcessBuilder pb; + double version = Double.parseDouble(System.getProperty("java.specification.version")); + if (version > 1.8D) { + pb = buildProcess(javaCommand, thisJar, descriptor.getId(), args); + } else { + File toolsJar = WhereIsUtils.findToolsJar(); + if (null == toolsJar) { + throw new Exception("Can not locate tools.jar file, unable to start attach mode."); + } + + pb = buildProcess(javaCommand, thisJar, descriptor.getId(), args, toolsJar); + } + + int exitValue = ProcessUtils.start(pb); + if (0 != exitValue) { + throw new Exception("Attach mode failed: " + exitValue); + } + } + + private static ProcessBuilder buildProcess(File java, File thisJar, String id, String args) { + String[] cmdArray = new String[]{ + java.getAbsolutePath(), + "-Djanf.debug=" + System.getProperty("janf.debug", "0"), + "-jar", + thisJar.getAbsolutePath(), + Launcher.ATTACH_ARG, + id, args + }; + + return new ProcessBuilder(cmdArray); + } + + private static ProcessBuilder buildProcess(File java, File thisJar, String id, String args, File toolsJar) { + String[] cmdArray = new String[]{ + java.getAbsolutePath(), + "-Djanf.debug=" + System.getProperty("janf.debug", "0"), + "-Xbootclasspath/a:" + toolsJar.getAbsolutePath(), + "-jar", + thisJar.getAbsolutePath(), + Launcher.ATTACH_ARG, + id, args + }; + + return new ProcessBuilder(cmdArray); + } +} diff --git a/src/main/java/com/janetfilter/core/attach/VMSelector.java b/src/main/java/com/janetfilter/core/attach/VMSelector.java new file mode 100644 index 0000000..4dbcbaf --- /dev/null +++ b/src/main/java/com/janetfilter/core/attach/VMSelector.java @@ -0,0 +1,124 @@ +package com.janetfilter.core.attach; + +import com.janetfilter.core.utils.DateUtils; +import com.janetfilter.core.utils.ProcessUtils; +import com.janetfilter.core.utils.WhereIsUtils; + +import java.io.*; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public class VMSelector { + private final File thisJar; + private List descriptors; + + public VMSelector(File thisJar) { + this.thisJar = thisJar; + } + + private List getVMList() throws Exception { + File jpsCommand = WhereIsUtils.findJPS(); + if (null == jpsCommand) { + throw new Exception("jps command not found"); + } + + List list = new ArrayList<>(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ProcessUtils.start(new ProcessBuilder(jpsCommand.getAbsolutePath(), "-lv"), bos); + + String line; + BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bos.toByteArray()))); + while ((line = reader.readLine()) != null) { + list.add(line); + } + + String processId = ProcessUtils.currentId(); + return list.stream() + .map(s -> { + String[] section = (s + " ").split(" ", 3); + return new VMDescriptor(section[0].trim(), section[1].trim(), section[2].trim()); + }) + .filter(d -> !d.getId().equals(processId) && !"sun.tools.jps.Jps".equals(d.getClassName()) && !"jdk.jcmd/sun.tools.jps.Jps".equals(d.getClassName())) + .sorted(Comparator.comparingInt(d -> Integer.parseInt(d.getId()))) + .collect(Collectors.toList()); + } + + private String getInput() throws IOException { + return new BufferedReader(new InputStreamReader(System.in)).readLine().trim(); + } + + private void processSelect() throws Exception { + System.out.print(" Select: "); + String input = getInput(); + + switch (input) { + case "Q": + case "q": + System.exit(0); + case "R": + case "r": + System.out.println(" =========================== " + DateUtils.formatDateTime() + " ============================"); + select(); + return; + case "": + processSelect(); + return; + default: + int index; + try { + index = Integer.parseInt(input); + } catch (NumberFormatException e) { + invalidInput(input); + return; + } + + if (index < 1) { + invalidInput(input); + return; + } + + if (index > descriptors.size()) { + invalidInput(input); + return; + } + + System.out.print(" Agent args: "); + input = getInput(); + try { + VMLauncher.launch(thisJar, descriptors.get(index - 1), input); + } catch (Exception e) { + System.err.println("> Attach to: " + index + " failed."); + e.printStackTrace(System.err); + return; + } + break; + } + } + + private void invalidInput(String input) throws Exception { + System.err.println("> Invalid input: " + input); + processSelect(); + } + + public void select() throws Exception { + boolean first = null == descriptors; + List temp = getVMList(); + if (null != descriptors && !descriptors.isEmpty()) { + temp.forEach(d -> d.setOld(descriptors.stream().anyMatch(d1 -> d.getId().equals(d1.getId())))); + } + + descriptors = temp; + System.out.println(" Java Virtual Machine List: (Select and attach" + (first ? "" : ", + means the new one") + ")"); + + int index = 1; + for (VMDescriptor d : descriptors) { + System.out.printf(" %3d]:%s%s %s%n", index++, d.getOld() ? " " : "+", d.getId(), d.getClassName()); + } + System.out.println(" r]: "); + System.out.println(" q]: "); + + processSelect(); + } +} diff --git a/src/main/java/com/janetfilter/core/commons/ConfigParser.java b/src/main/java/com/janetfilter/core/commons/ConfigParser.java index a873038..c47fc62 100644 --- a/src/main/java/com/janetfilter/core/commons/ConfigParser.java +++ b/src/main/java/com/janetfilter/core/commons/ConfigParser.java @@ -80,13 +80,13 @@ public class ConfigParser { } map.get(lastSection).add(rule); - DebugInfo.output("Add section: " + lastSection + ", rule: " + rule); + DebugInfo.debug("Add section: " + lastSection + ", rule: " + rule); break; } } } - DebugInfo.output("Config file loaded: " + file); + DebugInfo.debug("Config file loaded: " + file); return map; } } diff --git a/src/main/java/com/janetfilter/core/commons/DebugInfo.java b/src/main/java/com/janetfilter/core/commons/DebugInfo.java index 7127313..1d1e990 100644 --- a/src/main/java/com/janetfilter/core/commons/DebugInfo.java +++ b/src/main/java/com/janetfilter/core/commons/DebugInfo.java @@ -1,18 +1,95 @@ package com.janetfilter.core.commons; import com.janetfilter.core.utils.DateUtils; +import com.janetfilter.core.utils.ProcessUtils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; public class DebugInfo { - private static final boolean DEBUG = "1".equals(System.getenv("JANF_DEBUG")) || "1".equals(System.getProperty("janf.debug")); private static final String CLASS_NAME = DebugInfo.class.getName(); - private static final String LOG_TEMPLATE = "[%s] %s DEBUG : %s%n"; + private static final String LOG_TEMPLATE = "[%s] %s %s : %s%n"; + private static final Level LOG_LEVEL; + private static File logFile; + + static { + Level level = Level.of(System.getProperty("janf.debug")); + LOG_LEVEL = Level.NONE == level ? Level.of(System.getenv("JANF_DEBUG")) : level; + } + + public static void useFile(File dir) { + if (LOG_LEVEL == Level.NONE || null == dir) { + return; + } + + if (!dir.exists() && !dir.mkdirs()) { + error("Can't make directory: " + dir); + return; + } + + if (!dir.isDirectory()) { + error("It's not a directory: " + dir); + return; + } + + if (!dir.canWrite()) { + error("Read-only directory: " + dir); + return; + } + + File file = new File(dir, String.format("%s-%s.log", DateUtils.formatDate(), ProcessUtils.currentId())); + if (file.exists()) { + error("Log file exists: " + file); + return; + } + + logFile = file; + } + + public static void debug(String content, Throwable e) { + output(Level.DEBUG, content, e); + } + + public static void debug(String content) { + debug(content, null); + } + + public static void info(String content, Throwable e) { + output(Level.INFO, content, e); + } + + public static void info(String content) { + info(content, null); + } + + public static void warn(String content, Throwable e) { + output(Level.WARN, content, e); + } + + public static void warn(String content) { + warn(content, null); + } + + public static void error(String content, Throwable e) { + output(Level.ERROR, content, e); + } + + public static void error(String content) { + error(content, null); + } public static void output(String content) { - output(content, null); + debug(content); } public static void output(String content, Throwable e) { // No logger lib required - if (!DEBUG) { + debug(content, e); + } + + public static void output(Level level, String content, Throwable e) { // No logger lib required + if (Level.NONE == LOG_LEVEL || level.ordinal() < LOG_LEVEL.ordinal()) { return; } @@ -26,15 +103,74 @@ public class DebugInfo { } } - String outContent = String.format(LOG_TEMPLATE, DateUtils.formatNow(), caller, content); + String outContent = String.format(LOG_TEMPLATE, DateUtils.formatDateTime(), caller, level, content); if (null == e) { - System.out.print(outContent); + writeContent(outContent); return; } synchronized (DebugInfo.class) { - System.out.print(outContent); - e.printStackTrace(System.err); + writeContent(outContent, System.err); + writeException(e); + } + } + + private static void writeContent(String content) { + writeContent(content, System.out); + } + + private static void writeContent(String content, PrintStream fallback) { + if (null == logFile) { + fallback.print(content); + return; + } + + try (PrintStream ps = new PrintStream(new FileOutputStream(logFile, true))) { + ps.print(content); + } catch (IOException e) { + fallback.println(content); + } + } + + private static void writeException(Throwable e) { + writeException(e, System.err); + } + + private static void writeException(Throwable e, PrintStream fallback) { + if (null == logFile) { + e.printStackTrace(fallback); + return; + } + + try (PrintStream ps = new PrintStream(new FileOutputStream(logFile, true))) { + e.printStackTrace(ps); + } catch (IOException ex) { + e.printStackTrace(fallback); + } + } + + private enum Level { + NONE, DEBUG, INFO, WARN, ERROR; + + public static Level of(String valueStr) { + if (null == valueStr) { + return NONE; + } + + int value; + try { + value = Integer.parseInt(valueStr); + } catch (NumberFormatException e) { + return NONE; + } + + for (Level level : values()) { + if (level.ordinal() == value) { + return level; + } + } + + return NONE; } } } diff --git a/src/main/java/com/janetfilter/core/plugin/PluginManager.java b/src/main/java/com/janetfilter/core/plugin/PluginManager.java index 264637e..54c1a53 100644 --- a/src/main/java/com/janetfilter/core/plugin/PluginManager.java +++ b/src/main/java/com/janetfilter/core/plugin/PluginManager.java @@ -52,9 +52,9 @@ public final class PluginManager { throw new RuntimeException("Load plugin timeout"); } - DebugInfo.output(String.format("============ All plugins loaded, %.2fs elapsed ============", (System.currentTimeMillis() - startTime) / 1000D)); + DebugInfo.debug(String.format("============ All plugins loaded, %.2fs elapsed ============", (System.currentTimeMillis() - startTime) / 1000D)); } catch (Throwable e) { - DebugInfo.output("Load plugin failed", e); + DebugInfo.error("Load plugin failed", e); } } @@ -93,9 +93,9 @@ public final class PluginManager { dispatcher.addTransformers(pluginEntry.getTransformers()); - DebugInfo.output("Plugin loaded: {name=" + pluginEntry.getName() + ", version=" + pluginEntry.getVersion() + ", author=" + pluginEntry.getAuthor() + "}"); + DebugInfo.debug("Plugin loaded: {name=" + pluginEntry.getName() + ", version=" + pluginEntry.getVersion() + ", author=" + pluginEntry.getAuthor() + "}"); } catch (Throwable e) { - DebugInfo.output("Parse plugin info failed", e); + DebugInfo.error("Parse plugin info failed", e); } } } diff --git a/src/main/java/com/janetfilter/core/utils/DateUtils.java b/src/main/java/com/janetfilter/core/utils/DateUtils.java index 0ce40a0..c798b98 100644 --- a/src/main/java/com/janetfilter/core/utils/DateUtils.java +++ b/src/main/java/com/janetfilter/core/utils/DateUtils.java @@ -14,10 +14,18 @@ public class DateUtils { return FULL_DF.format(date); } + public static String formatDateTime() { + return FULL_DF.format(new Date()); + } + public static String formatDate(Date date) { return DATE_DF.format(date); } + public static String formatDate() { + return formatDate(new Date()); + } + public static String formatTime(Date date) { return TIME_DF.format(date); } @@ -33,8 +41,4 @@ public class DateUtils { public static Date parseDateTime(String dateTimeStr) throws ParseException { return FULL_DF.parse(dateTimeStr); } - - public static String formatNow() { - return formatDateTime(new Date()); - } } diff --git a/src/main/java/com/janetfilter/core/utils/ProcessUtils.java b/src/main/java/com/janetfilter/core/utils/ProcessUtils.java new file mode 100644 index 0000000..f4a1fb8 --- /dev/null +++ b/src/main/java/com/janetfilter/core/utils/ProcessUtils.java @@ -0,0 +1,75 @@ +package com.janetfilter.core.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.management.ManagementFactory; +import java.util.ArrayList; +import java.util.List; + +public class ProcessUtils { + private static String processId; + + public synchronized static String currentId() { + if (null == processId) { + String name = ManagementFactory.getRuntimeMXBean().getName() + "@"; + + processId = name.split("@", 2)[0]; + } + + return processId; + } + + public static int start(ProcessBuilder pb) throws Exception { + return start(pb, System.out, System.err); + } + + public static int start(ProcessBuilder pb, OutputStream out) throws Exception { + return start(pb, out, null); + } + + public static int start(ProcessBuilder pb, OutputStream out, OutputStream err) throws Exception { + Process p = pb.start(); + + List threads = new ArrayList<>(); + if (null != out) { + threads.add(new Thread(new RedirectOutput(p.getInputStream(), out))); + } + if (null != err) { + threads.add(new Thread(new RedirectOutput(p.getErrorStream(), err))); + } + + for (Thread thread : threads) { + thread.start(); + } + for (Thread thread : threads) { + thread.join(); + } + + return p.waitFor(); + } + + static class RedirectOutput implements Runnable { + private static final int BUFF_SIZE = 1024; + private final InputStream origin; + private final OutputStream dest; + + RedirectOutput(InputStream origin, OutputStream dest) { + this.origin = origin; + this.dest = dest; + } + + public void run() { + int length; + byte[] buffer = new byte[BUFF_SIZE]; + + try { + while ((length = origin.read(buffer)) != -1) { + dest.write(buffer, 0, length); + } + } catch (IOException e) { + throw new RuntimeException("ERROR: Redirect output failed.", e); + } + } + } +} diff --git a/src/main/java/com/janetfilter/core/utils/WhereIsUtils.java b/src/main/java/com/janetfilter/core/utils/WhereIsUtils.java new file mode 100644 index 0000000..6f45e82 --- /dev/null +++ b/src/main/java/com/janetfilter/core/utils/WhereIsUtils.java @@ -0,0 +1,82 @@ +package com.janetfilter.core.utils; + +import com.janetfilter.core.Launcher; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URL; + +public class WhereIsUtils { + private static final String JAVA_HOME = System.getProperty("java.home"); + + public static File findJPS() { + String[] paths = new String[]{"bin/jps", "bin/jps.exe", "../bin/jps", "../bin/jps.exe"}; + + for (String path : paths) { + File file = new File(JAVA_HOME, path); + if (file.exists() && file.isFile() && file.canExecute()) { + return getCanonicalFile(file); + } + } + + return null; + } + + public static File findJava() { + String[] paths = new String[]{"bin/java", "bin/java.exe", "../bin/java", "../bin/java.exe"}; + + for (String path : paths) { + File file = new File(JAVA_HOME, path); + if (file.exists() && file.isFile() && file.canExecute()) { + return getCanonicalFile(file); + } + } + + return null; + } + + public static File findToolsJar() { + String[] paths = new String[]{"lib/tools.jar", "../lib/tools.jar", "../../lib/tools.jar"}; + + for (String path : paths) { + File file = new File(JAVA_HOME, path); + if (file.exists() && file.isFile()) { + return getCanonicalFile(file); + } + } + + return null; + } + + public static URI getJarURI() throws Exception { + URL url = Launcher.class.getProtectionDomain().getCodeSource().getLocation(); + if (null != url) { + return url.toURI(); + } + + String resourcePath = "/288daf08f4ba46dfde71b7f0624b0ad7f234a67a.txt"; + url = Launcher.class.getResource(resourcePath); + if (null == url) { + throw new Exception("Can not locate resource file."); + } + + String path = url.getPath(); + if (!path.endsWith("!" + resourcePath)) { + throw new Exception("Invalid resource path."); + } + + path = path.substring(0, path.length() - resourcePath.length() - 1); + + return new URI(path); + + } + + private static File getCanonicalFile(File file) { + try { + return file.getCanonicalFile(); + } catch (IOException e) { + return null; + } + } +} diff --git a/src/main/resources/4cc9c353c626d6510ca855ab6907ed7f64400257.txt b/src/main/resources/288daf08f4ba46dfde71b7f0624b0ad7f234a67a.txt similarity index 100% rename from src/main/resources/4cc9c353c626d6510ca855ab6907ed7f64400257.txt rename to src/main/resources/288daf08f4ba46dfde71b7f0624b0ad7f234a67a.txt