提交 8df69e5a authored 作者: Thomas Mueller's avatar Thomas Mueller

Issue 467: OSGi Class Loader (ability to create reference to class in other…

Issue 467: OSGi Class Loader (ability to create reference to class in other ClassLoader, for example in another OSGi bundle).
上级 eb6754a8
...@@ -18,7 +18,8 @@ Change Log ...@@ -18,7 +18,8 @@ Change Log
<h1>Change Log</h1> <h1>Change Log</h1>
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul><li>- <ul><li>Issue 467: OSGi Class Loader (ability to create reference to class
in other ClassLoader, for example in another OSGi bundle).
</li></ul> </li></ul>
<h2>Version 1.3.173 (2013-07-28)</h2> <h2>Version 1.3.173 (2013-07-28)</h2>
......
...@@ -36,6 +36,7 @@ Import-Package: javax.management, ...@@ -36,6 +36,7 @@ Import-Package: javax.management,
org.h2.jdbcx;version="[${version},1.4.0)", org.h2.jdbcx;version="[${version},1.4.0)",
org.h2.tools;version="[${version},1.4.0)", org.h2.tools;version="[${version},1.4.0)",
org.h2.util;version="[${version},1.4.0)", org.h2.util;version="[${version},1.4.0)",
org.h2.value;version="[${version},1.4.0)",
org.osgi.framework;version="1.5", org.osgi.framework;version="1.5",
org.osgi.service.jdbc;version="1.0", org.osgi.service.jdbc;version="1.0",
org.slf4j;version="[1.6.0,1.7.0)";resolution:=optional org.slf4j;version="[1.6.0,1.7.0)";resolution:=optional
...@@ -46,5 +47,6 @@ Export-Package: org.h2;version="${version}", ...@@ -46,5 +47,6 @@ Export-Package: org.h2;version="${version}",
org.h2.jdbc;version="${version}", org.h2.jdbc;version="${version}",
org.h2.jdbcx;version="${version}", org.h2.jdbcx;version="${version}",
org.h2.tools;version="${version}", org.h2.tools;version="${version}",
org.h2.util;version="${version}" org.h2.util;version="${version}",
org.h2.value;version="${version}"
Premain-Class: org.h2.util.Profiler Premain-Class: org.h2.util.Profiler
...@@ -5,18 +5,24 @@ ...@@ -5,18 +5,24 @@
*/ */
package org.h2.util; package org.h2.util;
import java.util.Properties;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext; import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.jdbc.DataSourceFactory; import org.osgi.service.jdbc.DataSourceFactory;
import java.util.Properties;
/** /**
* The driver activator loads the H2 driver when starting the bundle. The driver * The driver activator loads the H2 driver when starting the bundle. The driver
* is unloaded when stopping the bundle. * is unloaded when stopping the bundle.
*/ */
public class DbDriverActivator implements BundleActivator { public class DbDriverActivator implements BundleActivator {
private OSGIClassFactory osgiClassFactory;
private OSGIServiceClassFactory osgiServiceClassFactory;
/** /**
* Start the bundle. This will load the database driver and register the * Start the bundle. This will load the database driver and register the
* DataSourceFactory service. * DataSourceFactory service.
...@@ -26,6 +32,10 @@ public class DbDriverActivator implements BundleActivator { ...@@ -26,6 +32,10 @@ public class DbDriverActivator implements BundleActivator {
@Override @Override
public void start(BundleContext bundleContext) { public void start(BundleContext bundleContext) {
org.h2.Driver driver = org.h2.Driver.load(); org.h2.Driver driver = org.h2.Driver.load();
osgiClassFactory = new OSGIClassFactory(bundleContext);
osgiServiceClassFactory = new OSGIServiceClassFactory(bundleContext);
Utils.addClassFactory(osgiClassFactory);
Utils.addClassFactory(osgiServiceClassFactory);
Properties properties = new Properties(); Properties properties = new Properties();
properties.put(DataSourceFactory.OSGI_JDBC_DRIVER_CLASS, org.h2.Driver.class.getName()); properties.put(DataSourceFactory.OSGI_JDBC_DRIVER_CLASS, org.h2.Driver.class.getName());
properties.put(DataSourceFactory.OSGI_JDBC_DRIVER_NAME, "H2 JDBC Driver"); properties.put(DataSourceFactory.OSGI_JDBC_DRIVER_NAME, "H2 JDBC Driver");
...@@ -42,7 +52,121 @@ public class DbDriverActivator implements BundleActivator { ...@@ -42,7 +52,121 @@ public class DbDriverActivator implements BundleActivator {
*/ */
@Override @Override
public void stop(BundleContext bundleContext) { public void stop(BundleContext bundleContext) {
Utils.removeClassFactory(osgiClassFactory);
Utils.removeClassFactory(osgiServiceClassFactory);
org.h2.Driver.unload(); org.h2.Driver.unload();
osgiClassFactory = null;
}
/**
* Extend the H2 class loading in order to find class defined in other bundles.
*
* The class format for bundle class is the following:
* BundleSymbolicName:BundleVersion:BinaryClassName
*/
private static class OSGIClassFactory implements Utils.ClassFactory {
/**
* Separator character to merge bundle name,version and class binary
* name
*/
public static final String SEPARATOR = ":";
private static final int BUNDLE_SYMBOLIC_NAME_INDEX = 0;
private static final int BUNDLE_VERSION_INDEX = 1;
private static final int BINARY_CLASS_NAME_INDEX = 2;
private BundleContext bundleContext;
/**
* Constructor
*
* @param bundleContext Valid bundleContext instance
*/
OSGIClassFactory(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
@Override
public boolean match(String name) {
// OSGi binary class name must contain two SEPARATOR character
int index = name.indexOf(SEPARATOR);
return !(index == -1 || name.indexOf(SEPARATOR, index + 1) == -1);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
String[] parts = name.split(SEPARATOR);
if (parts.length != 3) {
throw new ClassNotFoundException(
"OSGi class binary name must contain only 2 '"
+ SEPARATOR + "' characters");
}
for (Bundle bundle : bundleContext.getBundles()) {
if (bundle.getSymbolicName().equals(
parts[BUNDLE_SYMBOLIC_NAME_INDEX])
&& bundle.getVersion().toString()
.equals(parts[BUNDLE_VERSION_INDEX])) {
// Found the right bundle
return bundle.loadClass(parts[BINARY_CLASS_NAME_INDEX]);
}
}
throw new ClassNotFoundException("OSGi Bundle not found "
+ parts[BUNDLE_SYMBOLIC_NAME_INDEX] + " "
+ parts[BUNDLE_VERSION_INDEX]);
}
}
/**
* Extend the H2 class loading in order to find class defined in other bundles.
*
* The Class must be registered as a service.
*
* The main difference with {@link OSGIClassFactory} is that it does not rely to a specific bundle.
* OSGIServiceClassFactory is the preferred way to register function used in table constraints,
* theses functions should not be removed of the DataBase.
*
* The class format for bundle service class is the following :
* OSGI:BinaryClassNameService
*/
private static class OSGIServiceClassFactory implements Utils.ClassFactory {
/**
* Separator character to merge bundle name, version and class binary
* name
*/
public static final String SEPARATOR = "=";
private static final int BINARY_CLASS_NAME_INDEX = 1;
private BundleContext bundleContext;
/**
* Constructor
* @param bundleContext Valid bundleContext instance
*/
OSGIServiceClassFactory(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
@Override
public boolean match(String name) {
return name.startsWith("OSGI" + SEPARATOR);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
String[] parts = name.split(SEPARATOR);
if (parts.length != 2) {
throw new ClassNotFoundException(
"OSGi class binary name must contain only 1 '"
+ SEPARATOR + "' characters");
}
ServiceReference serviceReference = bundleContext
.getServiceReference(parts[BINARY_CLASS_NAME_INDEX]);
if (serviceReference != null) {
return bundleContext.getService(serviceReference).getClass();
}
throw new ClassNotFoundException("OSGi Service not found "+parts[BINARY_CLASS_NAME_INDEX]);
}
} }
} }
...@@ -21,8 +21,10 @@ import java.util.Arrays; ...@@ -21,8 +21,10 @@ import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import org.h2.api.JavaObjectSerializer; import org.h2.api.JavaObjectSerializer;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties; import org.h2.constant.SysProperties;
...@@ -61,6 +63,10 @@ public class Utils { ...@@ -61,6 +63,10 @@ public class Utils {
private static boolean allowAllClasses; private static boolean allowAllClasses;
private static HashSet<String> allowedClassNames; private static HashSet<String> allowedClassNames;
/**
* In order to manage more than one class loader
*/
private static List<ClassFactory> userClassFactories = new ArrayList<ClassFactory>();
private static String[] allowedClassNamePrefixes; private static String[] allowedClassNamePrefixes;
static { static {
...@@ -78,6 +84,22 @@ public class Utils { ...@@ -78,6 +84,22 @@ public class Utils {
// utility class // utility class
} }
/**
* Add a class factory in order to manage more than one class loader.
* @param classFactory An object that implements ClassFactory
*/
public static void addClassFactory(ClassFactory classFactory) {
userClassFactories.add(classFactory);
}
/**
* Remove a class factory
* @param classFactory Already inserted class factory instance
*/
public static void removeClassFactory(ClassFactory classFactory) {
userClassFactories.remove(classFactory);
}
private static int readInt(byte[] buff, int pos) { private static int readInt(byte[] buff, int pos) {
return (buff[pos++] << 24) + ((buff[pos++] & 0xff) << 16) + ((buff[pos++] & 0xff) << 8) + (buff[pos] & 0xff); return (buff[pos++] << 24) + ((buff[pos++] & 0xff) << 16) + ((buff[pos++] & 0xff) << 8) + (buff[pos] & 0xff);
} }
...@@ -569,18 +591,20 @@ public class Utils { ...@@ -569,18 +591,20 @@ public class Utils {
throw DbException.get(ErrorCode.ACCESS_DENIED_TO_CLASS_1, className); throw DbException.get(ErrorCode.ACCESS_DENIED_TO_CLASS_1, className);
} }
} }
// the following code might be better for OSGi (need to verify): // Use provided class factory first.
/* for (ClassFactory classFactory : userClassFactories) {
if (classFactory.match(className)) {
try { try {
return Utils.class.getClassLoader().loadClass(className); Class<?> userClass = classFactory.loadClass(className);
} catch (Throwable e) { if (!(userClass == null)) {
try { return userClass;
return Thread.currentThread().getContextClassLoader().loadClass(className); }
} catch (Throwable e2) { } catch (Exception e) {
DbException.get(ErrorCode.CLASS_NOT_FOUND_1, e, className); throw DbException.get(ErrorCode.CLASS_NOT_FOUND_1, e, className);
} }
} }
*/ }
// Use local ClassLoader
try { try {
return Class.forName(className); return Class.forName(className);
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
...@@ -878,4 +902,31 @@ public class Utils { ...@@ -878,4 +902,31 @@ public class Utils {
return defaultValue; return defaultValue;
} }
/**
* The utility methods will try to use the provided class factories to
* convert binary name of class to Class object. Used by H2 OSGi Activator
* in order to provide a class from another bundle ClassLoader.
*/
public interface ClassFactory {
/**
* Check whether the factory can return the named class.
*
* @param name the binary name of the class
* @return true if this factory can return a valid class for the
* provided class name
*/
boolean match(String name);
/**
* Load the class.
*
* @param name the binary name of the class
* @return the class object
* @throws ClassNotFoundException If the class is not handle by this
* factory
*/
Class<?> loadClass(String name)
throws ClassNotFoundException;
}
} }
...@@ -17,6 +17,7 @@ import java.sql.Types; ...@@ -17,6 +17,7 @@ import java.sql.Types;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.tools.SimpleResultSet; import org.h2.tools.SimpleResultSet;
import org.h2.util.Utils;
/** /**
* Tests for the CallableStatement class. * Tests for the CallableStatement class.
...@@ -39,6 +40,7 @@ public class TestCallableStatement extends TestBase { ...@@ -39,6 +40,7 @@ public class TestCallableStatement extends TestBase {
testCallWithResultSet(conn); testCallWithResultSet(conn);
testCallWithResult(conn); testCallWithResult(conn);
testPrepare(conn); testPrepare(conn);
testClassLoader(conn);
conn.close(); conn.close();
deleteDb("callableStatement"); deleteDb("callableStatement");
} }
...@@ -147,6 +149,28 @@ public class TestCallableStatement extends TestBase { ...@@ -147,6 +149,28 @@ public class TestCallableStatement extends TestBase {
getString("X"); getString("X");
} }
private void testClassLoader(Connection conn) throws SQLException {
Utils.ClassFactory myFactory = new TestClassFactory();
Utils.addClassFactory(myFactory);
try {
Statement stat = conn.createStatement();
stat.execute("CREATE ALIAS TCLASSLOADER FOR \"TestClassFactory.testClassF\"");
ResultSet rs = stat.executeQuery("SELECT TCLASSLOADER(true)");
assertTrue(rs.next());
assertEquals(false, rs.getBoolean(1));
} finally {
Utils.removeClassFactory(myFactory);
}
}
/**
* Class factory unit test
* @param b boolean value
* @return !b
*/
public static Boolean testClassF(Boolean b) {
return !b;
}
/** /**
* This method is called via reflection from the database. * This method is called via reflection from the database.
* *
...@@ -168,4 +192,19 @@ public class TestCallableStatement extends TestBase { ...@@ -168,4 +192,19 @@ public class TestCallableStatement extends TestBase {
return rs; return rs;
} }
/**
* A class factory used for testing.
*/
static class TestClassFactory implements Utils.ClassFactory {
@Override
public boolean match(String name) {
return name.equals("TestClassFactory");
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
return TestCallableStatement.class;
}
}
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论