提交 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
<h1>Change Log</h1>
<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>
<h2>Version 1.3.173 (2013-07-28)</h2>
......
......@@ -36,6 +36,7 @@ Import-Package: javax.management,
org.h2.jdbcx;version="[${version},1.4.0)",
org.h2.tools;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.service.jdbc;version="1.0",
org.slf4j;version="[1.6.0,1.7.0)";resolution:=optional
......@@ -46,5 +47,6 @@ Export-Package: org.h2;version="${version}",
org.h2.jdbc;version="${version}",
org.h2.jdbcx;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
......@@ -5,18 +5,24 @@
*/
package org.h2.util;
import java.util.Properties;
import org.h2.engine.Constants;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.jdbc.DataSourceFactory;
import java.util.Properties;
/**
* The driver activator loads the H2 driver when starting the bundle. The driver
* is unloaded when stopping the bundle.
*/
public class DbDriverActivator implements BundleActivator {
private OSGIClassFactory osgiClassFactory;
private OSGIServiceClassFactory osgiServiceClassFactory;
/**
* Start the bundle. This will load the database driver and register the
* DataSourceFactory service.
......@@ -26,6 +32,10 @@ public class DbDriverActivator implements BundleActivator {
@Override
public void start(BundleContext bundleContext) {
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.put(DataSourceFactory.OSGI_JDBC_DRIVER_CLASS, org.h2.Driver.class.getName());
properties.put(DataSourceFactory.OSGI_JDBC_DRIVER_NAME, "H2 JDBC Driver");
......@@ -42,7 +52,121 @@ public class DbDriverActivator implements BundleActivator {
*/
@Override
public void stop(BundleContext bundleContext) {
Utils.removeClassFactory(osgiClassFactory);
Utils.removeClassFactory(osgiServiceClassFactory);
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;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.h2.api.JavaObjectSerializer;
import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
......@@ -61,6 +63,10 @@ public class Utils {
private static boolean allowAllClasses;
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;
static {
......@@ -78,6 +84,22 @@ public class Utils {
// 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) {
return (buff[pos++] << 24) + ((buff[pos++] & 0xff) << 16) + ((buff[pos++] & 0xff) << 8) + (buff[pos] & 0xff);
}
......@@ -569,18 +591,20 @@ public class Utils {
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 {
return Utils.class.getClassLoader().loadClass(className);
} catch (Throwable e) {
try {
return Thread.currentThread().getContextClassLoader().loadClass(className);
} catch (Throwable e2) {
DbException.get(ErrorCode.CLASS_NOT_FOUND_1, e, className);
Class<?> userClass = classFactory.loadClass(className);
if (!(userClass == null)) {
return userClass;
}
} catch (Exception e) {
throw DbException.get(ErrorCode.CLASS_NOT_FOUND_1, e, className);
}
}
*/
}
// Use local ClassLoader
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
......@@ -878,4 +902,31 @@ public class Utils {
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;
import org.h2.constant.ErrorCode;
import org.h2.test.TestBase;
import org.h2.tools.SimpleResultSet;
import org.h2.util.Utils;
/**
* Tests for the CallableStatement class.
......@@ -39,6 +40,7 @@ public class TestCallableStatement extends TestBase {
testCallWithResultSet(conn);
testCallWithResult(conn);
testPrepare(conn);
testClassLoader(conn);
conn.close();
deleteDb("callableStatement");
}
......@@ -147,6 +149,28 @@ public class TestCallableStatement extends TestBase {
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.
*
......@@ -168,4 +192,19 @@ public class TestCallableStatement extends TestBase {
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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论