package jk_5.nailed.server.plugin;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.StringMemberValue;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.*;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class PluginDiscoverer {
private static final Logger logger = LogManager.getLogger();
private static Set<DiscoveredPlugin> discovered = new HashSet<DiscoveredPlugin>();
public static void clearDiscovered(){
discovered = new HashSet<DiscoveredPlugin>();
}
public static Set<DiscoveredPlugin> getDiscovered(){
return discovered;
}
//TODO: Generalize the file searching thing from below and use it in the jar loader
public static void discoverJarPlugins(File pluginsDir){
logger.info("Discovering plugins from the plugins folder...");
for(File file : pluginsDir.listFiles(new FilenameFilter(){
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".jar");
}
})){
try{
readJarFile(file, discovered);
}catch(IOException e){
}
}
}
public static void discoverClasspathPlugins(){
logger.info("Discovering plugins from the classpath...");
URL[] jars = ((URLClassLoader) PluginDiscoverer.class.getClassLoader()).getURLs();
for(URL jar : jars){
try{
File file = new File(jar.toURI());
if(file.isDirectory()){
logger.debug("Examining classPath directory " + file + " for potential plugins");
recurseChildren(file);
}else if(file.isFile()){
readJarFile(file, discovered);
}
}catch(Exception e){
}
}
}
private static void recurseChildren(File fi) throws IOException {
for(File f : fi.listFiles()){
if(f.isFile() && f.getName().endsWith(".class")){
FileInputStream fis = null;
try{
fis = new FileInputStream(f);
analizePotentialPlugins(fis, discovered, true, null);
}finally{
IOUtils.closeQuietly(fis);
}
}else if(f.isDirectory()){
recurseChildren(f);
}
}
}
private static void readJarFile(File file, Set<DiscoveredPlugin> discovered) throws IOException {
JarFile jar = new JarFile(file);
String jarName = jar.getName().toLowerCase();
if(!jarName.contains("jre") && !jarName.contains("jdk") && !jarName.contains("/rt.jar")){
logger.debug("Examining jar file {} for potential plugins", jar.getName());
Enumeration<JarEntry> entries = jar.entries();
while(entries.hasMoreElements()){
JarEntry entry = entries.nextElement();
if(entry != null && entry.getName().endsWith(".class")) { //Filter out junk we don't need. We only need class files
analizePotentialPlugins(jar.getInputStream(entry), discovered, false, file);
}
}
}else{
logger.debug("Ignoring JRE jar " + jar.getName());
}
}
private static void analizePotentialPlugins(InputStream input, Set<DiscoveredPlugin> discovered, boolean isClasspath, File file) throws IOException {
DataInputStream in;
if(input instanceof DataInputStream){
in = ((DataInputStream) input);
}else{
in = new DataInputStream(input);
}
ClassFile classFile = new ClassFile(in);
AnnotationsAttribute annotations = ((AnnotationsAttribute) classFile.getAttribute(AnnotationsAttribute.visibleTag));
if(annotations == null){
return;
}
for(Annotation annotation : annotations.getAnnotations()){
String annName = annotation.getTypeName();
if(annName.equals("jk_5.nailed.api.plugin.Plugin")){
StringMemberValue idValue = ((StringMemberValue) annotation.getMemberValue("id"));
String id = idValue == null ? null : idValue.getValue();
StringMemberValue nameValue = ((StringMemberValue) annotation.getMemberValue("name"));
String name = nameValue == null ? null : nameValue.getValue();
StringMemberValue versionValue = ((StringMemberValue) annotation.getMemberValue("version"));
String version = versionValue == null ? "unknown" : versionValue.getValue();
discovered.add(new DiscoveredPlugin(classFile.getName(), id, name, version, isClasspath, file));
}
}
}
static class DiscoveredPlugin {
public String className;
public String id;
public String name;
public String version;
public boolean isClasspath;
public File file;
public DiscoveredPlugin(String className, String id, String name, String version, boolean isClasspath, File file) {
this.className = className;
this.id = id;
this.name = name;
this.version = version;
this.isClasspath = isClasspath;
this.file = file;
}
}
}