@SuppressWarnings("rawtypes") private static <R, E extends Exception> R doContextAction(PluginClassLoader classloader, LimbusContextAction<R, E> callable) throws E { Lang.denyNull("Context action", callable); // Safe the current context classloader ClassLoader contextClassLoaderBefore = Thread.currentThread() .getContextClassLoader(); // Take the thread local before snaphsot. Set<ThreadLocal> beforeActionSnapshot = LimbusUtil.getCurrentThreadLocals(); try { // Set the plugin context Thread.currentThread() .setContextClassLoader(classloader); // Perform the actual action R retVal = callable.doAction(); // Return the actual compuation. return retVal; } finally { // Restore the old context classloader Thread.currentThread() .setContextClassLoader(contextClassLoaderBefore); // Take the thread local after snaphsot. Set<ThreadLocal> afterActionSnapshot = LimbusUtil.getCurrentThreadLocals(); // Remove all thread locals added by this context action. LimbusUtil.removeAddedThreadLocales(beforeActionSnapshot, afterActionSnapshot); LimbusUtil.addThreadLocals(beforeActionSnapshot); } }
static void denyClassNotFound(ClassLoader limbusClassloader, String className) throws LimbusClasspathException { try { getClass(limbusClassloader, className); } catch (ClassNotFoundException e) { throw new LimbusClasspathException(String.format("The class %s is not deployed on this classpath.", className), e); } }
@SuppressWarnings({ "rawtypes" }) static void storeThreadLocalsInDeployContext(Set<ThreadLocal> beforeActionSnapshot, Set<ThreadLocal> afterActionSnapshot, LimbusContextInternal limbusContext) { Set<ThreadLocal> threadLocalsDelta = removeAddedThreadLocales(beforeActionSnapshot, afterActionSnapshot); // Store the ThreadLocals added from the context action in limbus context limbusContext.setThreadLocalsSet(threadLocalsDelta); // Restore the ThreadLocals to the state before a context action was executed LimbusUtil.addThreadLocals(beforeActionSnapshot); }
@Override @SuppressWarnings("rawtypes") public <R, E extends Throwable> R doContextAction(LimbusContextAction<R, E> callable) throws E { Lang.denyNull("Context action", callable); // Safe the current context classloader ClassLoader contextClassLoaderBefore = Thread.currentThread() .getContextClassLoader(); // Take the thread local before snaphsot. Set<ThreadLocal> beforeActionSnapshot = getCurrentThreadLocals(); // Add the plugin's thread locals recorded before LimbusUtil.addThreadLocals(getThreadLocalsSet()); try { // Set the plugin context Thread.currentThread() .setContextClassLoader(getClassloader()); // Perform the actual action R retVal = callable.doAction(); // Return the actual compuation. return retVal; } finally { // Restore the old context classloader Thread.currentThread() .setContextClassLoader(contextClassLoaderBefore); // Take the thread local after snaphsot. Set<ThreadLocal> afterActionSnapshot = getCurrentThreadLocals(); // Do the combi-action of removing all added thread locals and store the added ones to the plugin's thread local // management. LimbusUtil.storeThreadLocalsInDeployContext(beforeActionSnapshot, afterActionSnapshot, this); } }
private <T extends LimbusPlugin> T createPlugin(String classname, Class<T> expectedType) throws LimbusClasspathException { // schuettec - 06.10.2016 : Class loading without running plugin code, no LimbusContextAction needed. denyClassNotFound(getClassloader(), classname); // schuettec - 06.10.2016 : Class loading without running plugin code, no LimbusContextAction needed. boolean isPlugin = isLimbusPlugin(getClassloader(), classname); if (isPlugin) { // schuettec - 06.10.2016 : LimbusContextAction is used internally in the following method. LimbusPlugin limbusPlugin = getLimbusPlugin(classname); return ReflectionUtil.getAsExpectedType(limbusPlugin, expectedType); } else { throw new LimbusClasspathException(String.format("The class %s is not a Limbus plugin.", classname)); } }
private void _deployPlugin(Classpath classpath, Set<Permission> permissions) throws LimbusException { if (deploymentMap.containsKey(classpath)) { return; } log.info("Deploy process started for plugin classpath."); LimbusUtil.logClasspath("plugin", classpath, log); LimbusUtil.logPermissions("plugin", permissions, log); PluginClassLoader pluginClassLoader = new PluginClassLoader(filesystem, sharedClassLoader, classpath.getClasspath()); pluginClassLoader.setPermissions(permissions); Deployment deployment = new Deployment(classpath, pluginClassLoader); deploymentMap.put(classpath, deployment); _deployClasspath(deployment); log.info("Deploy process finished successfully."); if (classpath.hasDeployName()) { deploynames.put(classpath.getDeployName(), classpath); } // Notify deployment subscribers deploymentListeners.multicastSilently() .classpathDeployed(classpath); }
@Override public void checkClasspath() throws LimbusClasspathException { try { filesystem.createFolder(LIB_FOLDER, false); List<URL> classpathURLs = filesystem.getFolderFiles(LIB_FOLDER); // Create the shared classpath with a speaking name to give hints about the creator. this.classpath = Classpath.create("sharedClassPath_" + getClass().getName()) .add(classpathURLs); LimbusUtil.logClasspath("shared", classpath, log); } catch (Exception e) { throw new LimbusClasspathException(String.format("Cannot create or access the shared classpath."), e); } }
/** * <p> * Public plugin APIs delivering classes used by plugins need to be accessible in the engine's classpath * because both * the engine and the plugins need to access the same classes. Other classes in the engine's classpath may not be * accessible to plugins. Therefore only a few packages are allowed to be accessible in this classpath. This packages * are defined by the most common package prefix. Every resource or class request is checked: If it accesses one of * the packages a prefix was defined for, access to this classes or resources is granted, otherwise the request is * delegated to the parent classloader of the engines classpath (which usually results in skipping the AppClassloader * and redirecting to its parent delivering the bootstrap classpath). * <p> * * <p> * <b>Note: The convention described above has a side-effect. The public API may not deliver resources in the root * package that must be available for plugins. The package prefix would be empty and this would grant access to the * whole engine classpath.</b> * </p> * * * @return Returns the list of allowed package prefixes to be accessed in the classpath of this engine. */ private final List<String> getAllowedPackagePrefixes() { String[] publicAccessPackages = getPublicAccessPackages(); Lang.denyNull("publicAccessPackages", publicAccessPackages); List<String> allowedPackagePrefixes = new LinkedList<String>(Arrays.asList(publicAccessPackages)); List<String> defaultAllowedPackagePrefixes = LimbusUtil.getDefaultAllowedPackagePrefixes(); allowedPackagePrefixes.addAll(defaultAllowedPackagePrefixes); return allowedPackagePrefixes; }
private void _undeployClasspath(Deployment deployment) { // Close the logging environment for this classpath if this classpath is not anonymous // Get the classloader before finishing the deployment (this will erase the reference) URLClassLoader classloader = deployment.getClassloader(); // Enqueue the classloader reference to the reference observer this.referenceObserver.observeReferenceTo(classloader); try { // Finish the deploy context and delete references deployment.finish(); } finally { // Close logTarget for plugin Classpath classpath = deployment.getClasspath(); if (classpath.hasDeployName()) { logTarget.closeChannel(classloader); } // Try to ad-hoc garbage collect the classloadfer. WeakReference<Object> classloaderWeakRef = new WeakReference<Object>(classloader); classloader = null; boolean garbageCollected = LimbusUtil.isGarbageCollected(classloaderWeakRef); if (garbageCollected) { log.info("Classloader was unloaded - the ad-hoc garbage collection was successful!"); } else { log.info( "The ad-hoc garbage collection was not successful - see log output of LimbusReferenceObserver to get long-term garbage collection notifications."); } } }
LimbusUtil.logClasspath("Plugin classpath", classpath, log); } else { log.error("Error while deploying limbus plugin - set log level to debug for more information.");
log.info("Undeploying anonymous classpath"); LimbusUtil.logClasspath("plugin", classpath, log); _undeployClasspath(deployment); log.info("Undeploy process finished successfully.");
static boolean isLimbusPlugin(ClassLoader limbusClassloader, String className) { Lang.denyNull("Classloader", limbusClassloader); Lang.denyNull("Classname", className); Class<?> clazz; try { // schuettec - 04.10.2016 : In the past we used Class.forName here (see next code line). This suffers from the // effect that classes loaded this way kept in memory for ever. // clazz = Class.forName(className, false, limbusClassloader); // schuettec - 06.10.2016 : Another thing to notice is, that here is not LimbusContextAction used to load the // class. This is because we do not initialize the class. We simply load it to know the type. No plugin code is // running here, so not LimbusContextAction is needed. clazz = getClass(limbusClassloader, className); return LimbusPlugin.class.isAssignableFrom(clazz) && !LimbusPlugin.class.equals(clazz) && !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers()); } catch (Throwable e) { // Ignore throwables is the only way to go here: We want to analyze classes for their types. This means the // classloader has to load them. He often will try to load classes that are not available in the classpath. Often // libraries have references to classes that are provided at runtime only if a specific feature is used at // runtime. Those optional classes will cause NoClassDefFoundErrors at this point. return false; } }