package act.boot.app; /*- * #%L * ACT Framework * %% * Copyright (C) 2014 - 2017 ActFramework * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import act.Constants; import act.boot.BootstrapClassLoader; import act.util.ActClassLoader; import act.util.ClassInfoRepository; import act.util.ClassNode; import act.util.Jars; import org.osgl.$; import org.osgl.util.*; import java.io.File; import java.lang.reflect.Modifier; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import java.util.Map; import static act.util.ClassInfoRepository.canonicalName; /** * This class loader is responsible for loading Act classes */ public class FullStackAppBootstrapClassLoader extends BootstrapClassLoader implements ActClassLoader { private static final String KEY_CLASSPATH = "java.class.path"; /** * the {@link System#getProperty(String) system property} key to get * the ignored jar file name prefix; multiple prefixes can be specified * with comma `,` * * The default value is defined in {@link #DEF_JAR_IGNORE} * `` */ private static final String KEY_SYS_JAR_IGNORE = "act.jar.sys.ignore"; /** * the {@link System#getProperty(String) system property} key to get * the ignored jar file name prefix; multiple prefixes can be specified * with comma `,` */ private static final String KEY_APP_JAR_IGNORE = "act.jar.app.ignore"; private static final String DEF_JAR_IGNORE = "act-asm,antlr,ecj-,cglib,commons-,hibernate-,jline-,kryo-,logback-," + "mongo-java-,mvel,newrelic,okio-,okhttp,pat-,proxytoys,rythm-engine,snakeyaml,undertow,xnio"; private final Class<?> PLUGIN_CLASS; private List<File> jars; private Long jarsChecksum; private Map<String, byte[]> libBC = C.newMap(); private List<Class<?>> actClasses = C.newList(); private List<Class<?>> pluginClasses = new ArrayList<Class<?>>(); private String lineSeparator = OS.get().lineSeparator(); private static final $.Predicate<File> jarFilter = jarFilter(); public FullStackAppBootstrapClassLoader(ClassLoader parent) { super(parent); preload(); PLUGIN_CLASS = $.classForName("act.plugin.Plugin", this); } @Override public List<Class<?>> pluginClasses() { if (classInfoRepository().isEmpty()) { restoreClassInfoRegistry(); restorePluginClasses(); if (classInfoRepository.isEmpty()) { for (String className : C.list(libBC.keySet())) { try { Class<?> c = loadClass(className, true); cache(c); int modifier = c.getModifiers(); if (Modifier.isAbstract(modifier) || !Modifier.isPublic(modifier) || c.isInterface()) { continue; } if (PLUGIN_CLASS.isAssignableFrom(c)) { pluginClasses.add(c); } } catch (ClassNotFoundException e) { // ignore } catch (NoClassDefFoundError e) { // ignore } } saveClassInfoRegistry(); savePluginClasses(); } } return pluginClasses; } public int libBCSize() { return libBC.size(); } protected void preload() { buildIndex(); } protected List<File> jars() { if (null == jars) { jars = jars(FullStackAppBootstrapClassLoader.class.getClassLoader()); jarsChecksum = calculateChecksum(jars); } return jars; } protected long jarsChecksum() { jars(); return jarsChecksum; } private static $.Predicate<File> jarFilter() { String ignores = System.getProperty(KEY_SYS_JAR_IGNORE); if (null == ignores) { ignores = DEF_JAR_IGNORE; } String appIgnores = System.getProperty(KEY_APP_JAR_IGNORE); if (null != appIgnores) { ignores += ("," + appIgnores); } final String[] sa = ignores.split(","); return new $.Predicate<File>() { @Override public boolean test(File file) { String name = file.getName(); for (String prefix : sa) { if (S.notBlank(prefix) && name.startsWith(prefix)) { return false; } } return true; } }; } private static List<File> filterJars(List<File> jars) { if (null == jarFilter) { return null; } return C.list(jars).filter(jarFilter); } public static List<File> jars(ClassLoader cl) { List<File> jars = null; C.List<String> path = C.listOf(System.getProperty(KEY_CLASSPATH).split(File.pathSeparator)); if (path.size() < 10) { if (cl instanceof URLClassLoader) { URLClassLoader realm = (URLClassLoader) cl; C.List<URL> urlList = C.listOf(realm.getURLs()); urlList = urlList.filter(new $.Predicate<URL>() { @Override public boolean test(URL url) { return url.getFile().endsWith(".jar"); } }); jars = urlList.map(new $.Transformer<URL, File>() { @Override public File transform(URL url) { try { return new File(url.toURI()); } catch (Exception e) { throw E.unexpected(e); } } }).sorted(); } } if (null == jars) { path = path.filter(S.F.contains("jre" + File.separator + "lib").negate().and(S.F.endsWith(".jar"))); jars = path.map(new $.Transformer<String, File>() { @Override public File transform(String s) { return new File(s); } }).sorted(); } return filterJars(jars); } private void saveClassInfoRegistry() { saveToFile(".act.class-registry", classInfoRepository().toJSON()); } private void savePluginClasses() { if (pluginClasses.isEmpty()) { return; } StringBuilder sb = S.builder(); for (Class c : pluginClasses) { sb.append(c.getName()).append(lineSeparator); } sb.deleteCharAt(sb.length() - 1); saveToFile(".act.plugins", sb.toString()); } private void saveToFile(String name, String content) { File file = new File(name); String fileContent = S.concat("#", S.string(jarsChecksum), lineSeparator, content); IO.writeContent(fileContent, file); } private void restoreClassInfoRegistry() { List<String> list = restoreFromFile(".act.class-registry"); if (list.isEmpty()) { return; } String json = S.join(lineSeparator, list); classInfoRepository = ClassInfoRepository.parseJSON(json); } private void restorePluginClasses() { List<String> list = restoreFromFile(".act.plugins"); if (list.isEmpty()) { return; } for (String s : list) { pluginClasses.add($.classForName(s, this)); } } private List<String> restoreFromFile(String name) { File file = new File(name); if (file.canRead()) { String content = IO.readContentAsString(file); String[] sa = content.split(lineSeparator); long fileChecksum = Long.parseLong(sa[0].substring(1)); if (jarsChecksum.equals(fileChecksum)) { return C.listOf(sa).drop(1); } } return C.list(); } private synchronized ClassNode cache(Class<?> c) { String cname = canonicalName(c); if (null == cname) { return null; } String name = c.getName(); ClassInfoRepository repo = (ClassInfoRepository)classInfoRepository(); if (repo.has(cname)) { return repo.node(name, cname); } actClasses.add(c); ClassNode node = repo.node(name); node.modifiers(c.getModifiers()); Class[] ca = c.getInterfaces(); for (Class pc: ca) { if (pc == Object.class) continue; String pcname = canonicalName(pc); if (null != pcname) { cache(pc); node.addInterface(pcname); } } Class pc = c.getSuperclass(); if (null != pc && Object.class != pc) { String pcname = canonicalName(pc); if (null != pcname) { cache(pc); node.parent(pcname); } } return node; } private void buildIndex() { libBC.putAll(Jars.buildClassNameIndex(jars())); } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class<?> c = findLoadedClass(name); if (null != c) { return c; } if (name.startsWith("java") || name.startsWith("org.osgl") || name.startsWith("org.slf4j")) { return super.loadClass(name, resolve); } if (!protectedClasses.contains(name)) { c = loadActClass(name, resolve); } if (null == c) { return super.loadClass(name, resolve); } else { return c; } } protected byte[] tryLoadResource(String name) { return null; } protected Class<?> loadActClass(String name, boolean resolve) { byte[] ba = libBC.remove(name); if (null == ba) { ba = tryLoadResource(name); } if (null == ba) { return findLoadedClass(name); } Class<?> c = null; if (name.startsWith(Constants.ACT_PKG) || name.startsWith(Constants.ASM_PKG)) { // skip bytecode enhancement for asm classes or non Act classes try { c = super.defineClass(name, ba, 0, ba.length, DOMAIN); } catch (NoClassDefFoundError e) { return null; } } if (null == c) { c = defineClass(name, ba); } if (resolve) { super.resolveClass(c); } return c; } public Class<?> createClass(String name, byte[] b) throws ClassFormatError { return super.defineClass(name, b, 0, b.length, DOMAIN); } public static long calculateChecksum(List<File> files) { long l = 0; for (File file : files) { l += file.hashCode() + file.lastModified(); } return l; } }