com.enioka.jqm.tools.ClassloaderManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jqm-engine Show documentation
Show all versions of jqm-engine Show documentation
A library containing the JQM engine. Cannot be used alone.
package com.enioka.jqm.tools;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.naming.NamingException;
import javax.naming.spi.NamingManager;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.enioka.jqm.jdbc.DbConn;
import com.enioka.jqm.model.Cl;
import com.enioka.jqm.model.GlobalParameter;
import com.enioka.jqm.model.JobDef;
import com.enioka.jqm.model.JobInstance;
/**
* This class holds all the {@link JarClassLoader} and is the only place to create one. There should be one instance per engine.
* We use a specific object rather than static objects in the {@link Loader} class to allow multiple engine instantiations. It also allows
* to centralise all CL creation methods and have cleaner code in Loader and in JCL.
*/
class ClassloaderManager
{
private Logger jqmlogger = LoggerFactory.getLogger(ClassloaderManager.class);
/**
* The CL corresponding to "one CL to rule them all" mode.
*/
private JarClassLoader sharedClassLoader = null;
/**
* The CL for loading plugins (for the engine)
*/
private ClassLoader pluginClassLoader = null;
private boolean hasPlugins = true;
/**
* The CLs corresponding to "one CL per jar" mode.
*/
private Map sharedJarClassLoader = new HashMap();
/**
* The CLs corresponding to specific keys (specified inside {@link JobDef#getSpecificIsolationContext()}). Key is Cl object ID.
*/
private Map persistentClassLoaders = new HashMap();
/**
* The different runners which may be involved inside the class loaders. Simple class names.
*/
private List runnerClasses = new ArrayList();
/**
* The default CL mode. Values can be: null, Shared, SharedJar.
*/
private String launchIsolationDefault = null;
private final LibraryResolverFS fsResolver;
private final LibraryResolverMaven mavenResolver;
ClassloaderManager()
{
this.fsResolver = new LibraryResolverFS();
this.mavenResolver = new LibraryResolverMaven();
}
void setIsolationDefault(DbConn cnx)
{
this.launchIsolationDefault = GlobalParameter.getParameter(cnx, "launch_isolation_default", "Isolated");
String rns = GlobalParameter.getParameter(cnx, "job_runners",
"com.enioka.jqm.tools.LegacyRunner,com.enioka.jqm.tools.MainRunner,com.enioka.jqm.tools.RunnableRunner");
for (String s : rns.split(","))
{
runnerClasses.add(s);
jqmlogger.info("Detected a job instance runner named " + s);
}
}
JarClassLoader getClassloader(JobInstance ji, DbConn cnx) throws MalformedURLException, JqmPayloadException, RuntimeException
{
final JarClassLoader jobClassLoader;
JobDef jd = ji.getJD();
// Extract the jar actual path
File jarFile = new File(FilenameUtils.concat(new File(ji.getNode().getRepo()).getAbsolutePath(), jd.getJarPath()));
// The parent class loader is normally the CL with EXT on its CL. But if no lib load, user current one (happens for external
// payloads)
ClassLoader parent = getParentClassLoader(ji);
// Priority is:
// 1 - a specific context
// 2 - general mode (jar or global)
Cl cldef = jd.getClassLoader();
if (cldef != null)
{
////////////////////////////////////
// Specific CL options were given
String clSharingKey = cldef.getName();
// We take great care to reduce locking as much as possible, so this code may seem to be too complicated at first glance.
if (cldef.isPersistent())
{
if (persistentClassLoaders.containsKey(cldef.getId()))
{
jqmlogger.info("Using an existing specific isolation context : " + clSharingKey);
jobClassLoader = persistentClassLoaders.get(cldef.getId());
}
else
{
synchronized (persistentClassLoaders)
{
if (persistentClassLoaders.containsKey(cldef.getId()))
{
jqmlogger.info("Using an existing specific isolation context : " + clSharingKey);
jobClassLoader = persistentClassLoaders.get(cldef.getId());
}
else
{
jqmlogger.info("Creating a new persistent specific isolation context: " + clSharingKey);
jobClassLoader = new JarClassLoader(parent);
jobClassLoader.setReferenceJobDefName(jd.getApplicationName());
jobClassLoader.mayBeShared(cldef.isPersistent());
jobClassLoader.setHiddenJavaClasses(cldef.getHiddenClasses());
jobClassLoader.setTracing(cldef.isTracingEnabled());
jobClassLoader.setChildFirstClassLoader(cldef.isChildFirst());
persistentClassLoaders.put(cldef.getId(), jobClassLoader);
}
}
}
}
else
{
jqmlogger.info("Creating a new transient specific isolation context: " + clSharingKey);
jobClassLoader = new JarClassLoader(parent);
jobClassLoader.setReferenceJobDefName(jd.getApplicationName());
jobClassLoader.mayBeShared(cldef.isPersistent());
jobClassLoader.setHiddenJavaClasses(cldef.getHiddenClasses());
jobClassLoader.setTracing(cldef.isTracingEnabled());
jobClassLoader.setChildFirstClassLoader(cldef.isChildFirst());
}
// END SPECIFIC CL OPTIONS
////////////////////////////////////
}
else
{
// Use default CL options. We accept double creation on edge sync states in this case.
if ("Shared".equals(launchIsolationDefault))
{
if (sharedClassLoader != null)
{
jqmlogger.info("Using sharedClassLoader");
jobClassLoader = sharedClassLoader;
}
else
{
jqmlogger.info("Creating sharedClassLoader");
jobClassLoader = new JarClassLoader(parent);
jobClassLoader.mayBeShared(true);
sharedClassLoader = jobClassLoader;
}
}
else if ("SharedJar".equals(launchIsolationDefault))
{
if (sharedJarClassLoader.containsKey(jd.getJarPath()))
{
// check if jarUrl has already a class loader
jqmlogger.info("Using shared Jar CL");
jobClassLoader = sharedJarClassLoader.get(jd.getJarPath());
}
else
{
jqmlogger.info("Creating shared Jar CL");
jobClassLoader = new JarClassLoader(parent);
jobClassLoader.mayBeShared(true);
sharedJarClassLoader.put(jd.getJarPath(), jobClassLoader);
}
}
else
{
// Standard case: all launches are independent. We create a transient CL.
jqmlogger.debug("Using an isolated transient CL with default parameters");
jobClassLoader = new JarClassLoader(parent);
jobClassLoader.mayBeShared(false);
}
}
// Resolve the libraries and add them to the classpath
final URL[] classpath = getClasspath(ji, cnx);
// Remember to also add the jar file itself... as CL can be shared, there is no telling if it already present or not.
jobClassLoader.extendUrls(jarFile.toURI().toURL(), classpath);
// Some debug display
jqmlogger.trace("CL URLs:");
for (URL url : jobClassLoader.getURLs())
{
jqmlogger.trace(" - " + url.toString());
}
return jobClassLoader;
}
private ClassLoader getExtensionClassloader()
{
ClassLoader extLoader = null;
try
{
extLoader = ((JndiContext) NamingManager.getInitialContext(null)).getExtCl();
}
catch (NamingException e)
{
jqmlogger.warn("could not find ext directory class loader. No parent classloader will be used", e);
}
return extLoader;
}
/**
* Returns all the URL that should be inside the classpath. This includes the jar itself if any.
*
* @throws JqmPayloadException
*/
private URL[] getClasspath(JobInstance ji, DbConn cnx) throws JqmPayloadException
{
switch (ji.getJD().getPathType())
{
case MAVEN:
return mavenResolver.resolve(ji, cnx);
case MEMORY:
return new URL[0];
case FS:
default:
return fsResolver.getLibraries(ji.getNode(), ji.getJD(), cnx);
}
}
private ClassLoader getParentClassLoader(JobInstance ji)
{
switch (ji.getJD().getPathType())
{
case MAVEN:
return getExtensionClassloader();
case MEMORY:
return Thread.currentThread().getContextClassLoader();
default:
case FS:
return getExtensionClassloader();
}
}
List getJobRunnerClasses()
{
return this.runnerClasses;
}
ClassLoader getPluginClassLoader()
{
if (hasPlugins && pluginClassLoader == null)
{
File extDir = new File("plugins/");
List urls = new ArrayList();
if (extDir.isDirectory())
{
for (File f : extDir.listFiles())
{
if (!f.canRead())
{
throw new RuntimeException("can't access file " + f.getAbsolutePath());
}
try
{
urls.add(f.toURI().toURL());
}
catch (MalformedURLException e)
{
jqmlogger.error("Error when parsing the content of plugin directory. File will be ignored", e);
}
}
// Create classloader
final URL[] aUrls = urls.toArray(new URL[0]);
for (URL u : aUrls)
{
jqmlogger.trace(u.toString());
}
pluginClassLoader = AccessController.doPrivileged(new PrivilegedAction()
{
@Override
public URLClassLoader run()
{
return new URLClassLoader(aUrls, null);
}
});
}
else
{
hasPlugins = false;
pluginClassLoader = ClassloaderManager.class.getClassLoader();
}
}
return pluginClassLoader;
}
}