Changeset 7192


Ignore:
Timestamp:
Aug 29, 2016, 2:47:49 PM (7 years ago)
Author:
Nicklas Nordborg
Message:

References #2027: Improve inter-communication between extensions

The original idea that X-Extension-Class-Path should specify extensions that are needed did not work. The extension system may load code (eg. ActionFactory and Action instances; see Three.java in this change) during the initialization of the extension system. The extensions are not registered until everything has been properly setup. If one extension then need to use another extension it will not be able to find it since it has not yet been registered.

The new approach is to simply only use the Class-Path attribute in the MANIFEST.MF file. If the referenced JAR file already exists it is no longer loaded immediately. Instead a proxy instance is created which will create the real class loader when needed. JAR files that are packages inside the META-INF/lib directory are not affected by this change. The code is still loaded before the extension system has been setup but since we now have the filename we do not have to ask the extension system to translate. The drawback is that extensions must be installed with the original filename.

This change also breaks the findResource() and hasChanged() methods which no longer searches the proxied JAR files.

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/build.xml

    r7190 r7192  
    709709      <metainf file="${test.src}/net/sf/basedb/test/extension/jar3/extensions.xml" />
    710710      <manifest>
    711         <attribute name="X-Extension-Class-Path" value="extension-in-jar1 extension-in-jar2" />
     711        <attribute name="Class-Path" value="ExtensionOne.jar ExtensionTwo.jar" />
    712712      </manifest>
    713713    </jar>
  • trunk/src/core/net/sf/basedb/util/JarClassLoader.java

    r6898 r7192  
    2828import java.util.Map;
    2929import java.util.HashMap;
     30import java.util.Iterator;
    3031import java.util.Enumeration;
    3132import java.util.Vector;
     
    135136  }
    136137 
    137  
    138138  /**
    139139    Get a new class loader for the specified jar file. 
     
    199199  */
    200200  private final Map<File, JarInfo> jarFiles;
     201   
     202  /**
     203    Class loaders for extensions.
     204  */
     205  private final List<JarClassLoaderProxy> proxyLoaders;
    201206 
    202207  /**
     
    232237    classPath = new HashMap<String, List<File>>();
    233238    jarFiles = new HashMap<File, JarInfo>();
     239    proxyLoaders = new ArrayList<JarClassLoaderProxy>();
    234240    loadJarFile(mainJarFile, true);
    235241  }
     
    338344   
    339345    // 2. Check the system class loader
    340     if (c == null && parent != system) c = loadClassInternal(system, name);
    341     if (log.isDebugEnabled()) log.debug("system class (" + name + "): " + c);
     346    if (c == null && parent != system)
     347    {
     348      c = loadClassInternal(system, name);
     349      if (log.isDebugEnabled()) log.debug("system class (" + name + "): " + c);
     350    }
    342351   
    343352    // 3. Delegate to parent class loader
    344     if (c == null && delegateFirst) c = loadClassInternal(parent, name);
    345     if (log.isDebugEnabled()) log.debug("parent class (" + name + "): " + c);
     353    if (c == null && delegateFirst)
     354    {
     355      c = loadClassInternal(parent, name);
     356      if (log.isDebugEnabled()) log.debug("parent class (" + name + "): " + c);
     357    }
    346358
    347359    // 4. Look for the class in this class path
     
    354366      catch (ClassNotFoundException ex)
    355367      {}
    356     }
    357     if (log.isDebugEnabled()) log.debug("my class (" + name + "): " + c);
     368      if (log.isDebugEnabled()) log.debug("my class (" + name + "): " + c);
     369    }
    358370   
    359371    // 5. Delegate to parent class loader if it hasn't been done
    360     if (c == null && !delegateFirst)  c = loadClassInternal(parent, name);
    361     if (log.isDebugEnabled()) log.debug("parent class (" + name + "): " + c);
     372    if (c == null && !delegateFirst)
     373    {
     374      c = loadClassInternal(parent, name);
     375      if (log.isDebugEnabled()) log.debug("parent class (" + name + "): " + c);
     376    }
     377   
     378    // 6. Side-load from proxied JAR files (eg. other extensions)
     379    Iterator<JarClassLoaderProxy> it = proxyLoaders.iterator();
     380    while (c == null && it.hasNext())
     381    {
     382      JarClassLoaderProxy proxyLoader = it.next();
     383      c = proxyLoader.findClass(name);
     384      if (log.isDebugEnabled()) log.debug("proxy class (" + proxyLoader.jarPath + "; " + name + "): " + c);
     385    }
    362386   
    363387    if (c == null)
     
    528552      if (followClassPath && manifest != null)
    529553      {
     554        File directory = file.getParentFile();
    530555        Attributes attr = manifest.getMainAttributes();
    531556        if (attr != null)
     
    540565              if (cps[i] != null && !cps[i].trim().equals(""))
    541566              {
    542                 File classPathFile = new File(file.getParent(), cps[i]);
     567                File classPathFile = new File(directory, cps[i]);
    543568                if (!classPathFile.exists())
    544569                {
     
    573598                    }
    574599                  }
     600                  loadJarFile(classPathFile, false);
    575601                }
    576                 loadJarFile(classPathFile, false);
     602                else
     603                {
     604                  proxyLoaders.add(new JarClassLoaderProxy(classPathFile.getAbsolutePath()));
     605                }
    577606              }
    578607            }
     
    728757    }
    729758  }
     759 
     760  /**
     761    A proxy class loader is typically needed when one extension
     762    depends on another extension. The proxy is needed since
     763    we want to use lazy initialization of the other class loaders.
     764    They may not be needed immediately. The proxy will also make it
     765    possible to avoid infinite recursion in case two extensions
     766    depend on each other.
     767    <p>
     768   
     769    Once a real class loader has been aquired it will be kept
     770    until the main class loader is discared. Note that the
     771    {@link JarClassLoader#hasChanged(boolean)} is not used for
     772    automatic reloading since that may cause class-cast exceptions.
     773    <p>
     774   
     775    The proxy is also used to limit the search for classes to
     776    classes that are directly handled directly by the other class
     777    loader. The system and parent class loaders are not searched again
     778    since they are already searched in the main class loader.
     779
     780    @since 3.10
     781  */
     782  static class JarClassLoaderProxy
     783  {
     784   
     785    final String jarPath;
     786    private JarClassLoader loader;
     787    private boolean isInitialized;
     788   
     789    /**
     790      Creates a proxy class loader for the given JAR file.
     791    */
     792    JarClassLoaderProxy(String jarPath)
     793    {
     794      this.jarPath = jarPath;
     795      this.isInitialized = false;
     796    }
     797   
     798    /**
     799      Initialize the proxy.
     800    */
     801    private synchronized void init()
     802    {
     803      if (isInitialized) return;
     804      if (log.isDebugEnabled()) log.debug("JarClassLoaderProxy.init[" + jarPath + "]");
     805     
     806      try
     807      {
     808        loader = (JarClassLoader)JarClassLoader.getInstance(jarPath, true);
     809      }
     810      catch (IOException ex)
     811      {}
     812      isInitialized = true;
     813    }
     814   
     815   
     816    Class<?> findClass(String name)
     817    {
     818      if (log.isDebugEnabled()) log.debug("JarClassLoaderProxy.findClass[" + jarPath + "]: " + name);
     819      if (!isInitialized) init();
     820     
     821      Class<?> c = null;
     822      if (loader != null)
     823      {
     824        try
     825        {
     826          // 1. Check if the class has already been loaded
     827          c = loader.findLoadedClass(name);
     828          if (log.isDebugEnabled()) log.debug("JarClassLoaderProxy.findClass[" + jarPath + "]: loaded class (" + name + "): " + c);
     829         
     830          // 2. Otherwise try to load the class without searching parent or system class loaders
     831          if (c == null)
     832          {
     833            c = loader.findClass(name);
     834            if (log.isDebugEnabled()) log.debug("JarClassLoaderProxy.findClass[" + jarPath + "]: found class (" + name + "): " + c);
     835          }
     836        }
     837        catch (ClassNotFoundException ex)
     838        {}
     839      }
     840      return c;
     841    }
     842  }
    730843}
  • trunk/src/test/net/sf/basedb/test/extension/jar3/Three.java

    r7190 r7192  
    3535{
    3636 
     37  private final One one;
     38  private final Two two;
     39 
    3740  public Three()
    38   {}
     41  {
     42    this.one = new One();
     43    this.two = new Two();
     44  }
    3945 
    4046  @Override
    4147  public String getTitle()
    4248  {
    43     return new One().getTitle() + "+" + new Two().getTitle() + "=Three";
     49    return one.getTitle() + "+" + two.getTitle() + "=Three";
    4450  }
    4551 
     
    5359  public int getType()
    5460  {
    55     return 3;
     61    return one.getType()+two.getType();
    5662  }
    5763 
Note: See TracChangeset for help on using the changeset viewer.