I have a working code that loads dynamically different Class Implementations with different Class Names. The class files are loaded into the in-memory database (Apache Derby Db), and classloader retrieve the .class file from the BLOB columns.
What I want to do is, inserting the .class files as binary BLOB with version column and IS_ENABLED
flags, then classloader will load the class for different versions on run-time. There will be db entries same amount of the compiled class versions and there will only one class with IS_ENABLED
flag set to TRUE.
Because that I try to load the same class name with custom classloader, I get the following Exception;
Exception in thread "main" java.lang.LinkageError: loader (instance of com/levent/classloader/DerbyServerClassLoader): attempted duplicate class definition for name: "com/levent/greeter/Greeter"
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(Unknown Source)
at java.lang.ClassLoader.defineClass(Unknown Source)
at com.levent.classloader.DerbyServerClassLoader.findClass(DerbyServerClassLoader.java:38)
at com.levent.example.ClientClassLoaderDBVersionDemo.main(ClientClassLoaderDBVersionDemo.java:43)
There are two different .class files (Greeter.class.v1, Greeter.class.v2) (inserted at the begining of the code) for the same Interface (Greeter.java)
In the beginning of test code, class files are retrieved from the lib/classes/ folder and inserted as blob binary data to the in-memory db, after then, .class files are sequentially retrieved by the database and loaded. While loading the class with same name, Exception occurs.
How can I solve this problem? Is there any way to unload a class, or else, anyway to reload a class with same name?
Project Tree
DerbyServerClassLoader.java - Class Loader
package com.levent.classloader;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DerbyServerClassLoader extends ClassLoader {
private ClassLoader parent;
private String connectionString;
public DerbyServerClassLoader(String connectionString) {
this(ClassLoader.getSystemClassLoader(), connectionString);
}
public DerbyServerClassLoader(ClassLoader parent, String connectionString) {
super(parent);
this.parent = parent;
this.connectionString = connectionString;
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
Class cls = null;
try {
cls = parent.loadClass(name); // Delegate to the parent Class Loader
} catch (ClassNotFoundException clnfE) { // If parent fails, try to locate and load the class
byte[] bytes = new byte[0];
try {
bytes = loadClassFromDatabase(name);
} catch (SQLException sqlE) {
throw new ClassNotFoundException("Unable to load class", sqlE);
}
return defineClass(name, bytes, 0, bytes.length);
}
return cls;
}
private byte[] loadClassFromDatabase(String name) throws SQLException {
PreparedStatement pstmt = null;
Connection connection = null;
try {
connection = DriverManager.getConnection(connectionString);
String sql = "SELECT CLASS FROM CLASSES WHERE CLASS_NAME = ? AND IS_ENABLED = ?";
pstmt = connection.prepareStatement(sql);
pstmt.setString(1, name);
pstmt.setBoolean(2, true);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
Blob blob = rs.getBlob(1);
byte[] data = blob.getBytes(1, (int) blob.length());
return data;
}
} catch (SQLException e) {
System.out.println("Unexpected exception: " + e.toString());
} catch (Exception e) {
System.out.println("Unexpected exception: " + e.toString());
} finally {
if (pstmt != null) {
pstmt.close();
}
if(connection != null) {
connection.close();
}
}
return null;
}
}
Greet.java - Greeter Interface
package com.levent.greeter;
public interface Greet {
public String getGreetMessage();
}
DbSingleton.java - Utility Class to connect DerbyDb
package com.levent.derbyutility;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DbSingleton {
private static DbSingleton instance = null;
private Connection conn = null;
private DbSingleton() {
try{
DriverManager.registerDriver(new org.apache.derby.jdbc.EmbeddedDriver());
} catch (SQLException e) {
e.printStackTrace();
}
}
public static DbSingleton getInstance() {
if(instance == null) {
synchronized(DbSingleton.class) {
if(instance == null) {
instance = new DbSingleton();
}
}
}
return instance;
}
public Connection getConnection() throws SQLException {
if(conn == null || conn.isClosed()) {
synchronized (DbSingleton.class) {
if(conn == null || conn.isClosed()) {
try{
//String dbUrl = "jdbc:derby://localhost:1527/myDB;create=true;user=me;password=mine";
String dbUrl = "jdbc:derby://localhost:1527/memory:myDB;create=true";
conn = DriverManager.getConnection(dbUrl);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
return conn;
}
}
ClientClassLoaderDBVersionDemo.java - Test Code
package com.levent.example;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import com.levent.classloader.DerbyServerClassLoader;
import com.levent.derbyutility.DbSingleton;
import com.levent.greeter.Greet;
public class ClientClassLoaderDBVersionDemo {
// apache derby in-memory db
private static final String connectionString = "jdbc:derby://localhost:1527/memory:myDB;create=true";
private static final String classFileName1 = "Greeter.class.v1";
private static final String classFileName2 = "Greeter.class.v2";
private static final String className = "com.levent.greeter.Greeter";
public static void main(String[] args) {
prepareClass();
try {
Greet greet = null;
DerbyServerClassLoader cl = new DerbyServerClassLoader(connectionString);
updateVersion(className, "v1");
Class clazz1 = cl.findClass(className);
greet = (Greet) clazz1.newInstance();
System.out.println("Version 1 Greet.getGreetMessage() : " + greet.getGreetMessage());
updateVersion(className, "v2");
Class clazz2 = cl.findClass(className);
greet = (Greet) clazz2.newInstance();
System.out.println("Version 2 Greet.getGreetMessage() : " + greet.getGreetMessage());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private static void prepareClass() {
DbSingleton instance = DbSingleton.getInstance();
Connection conn = null;
try {
conn = instance.getConnection();
} catch (SQLException e1) {
e1.printStackTrace();
}
Statement sta;
if (conn != null) {
try {
sta = conn.createStatement();
int count = sta
.executeUpdate("CREATE TABLE CLASSES (CLASS_NAME VARCHAR(50), CLASS BLOB, IS_ENABLED BOOLEAN, VERSION VARCHAR(10) )");
System.out.println("CLASSES Table created");
sta.close();
sta = conn.createStatement();
PreparedStatement psta = conn.prepareStatement("INSERT INTO CLASSES (CLASS_NAME, CLASS, IS_ENABLED, VERSION) values (?, ?, ?, ?)");
byte[] bytes = null;
InputStream blobObject = null;
psta.setString(1, className);
bytes = readJarFileAsByteArray(classFileName1);
blobObject = new ByteArrayInputStream(bytes);
psta.setBlob(2, blobObject, bytes.length);
psta.setBoolean(3, false);
psta.setString(4, "v1");
count = psta.executeUpdate();
psta.setString(1, className);
bytes = readJarFileAsByteArray(classFileName2);
blobObject = new ByteArrayInputStream(bytes);
psta.setBlob(2, blobObject, bytes.length);
psta.setBoolean(3, false);
psta.setString(4, "v2");
count += psta.executeUpdate();
System.out.println(count + " record(s) created.");
sta.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
private static byte[] readJarFileAsByteArray(String classFileName) {
Path currentRelativePath = Paths.get("");
String s = currentRelativePath.toAbsolutePath().toString();
File file = new File(s + "/lib/classes/" + classFileName);
byte[] fileData = new byte[(int) file.length()];
DataInputStream dis;
try {
dis = new DataInputStream(new FileInputStream(file));
dis.readFully(fileData);
dis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return fileData;
}
private static void updateVersion(String className, String version) {
DbSingleton instance = DbSingleton.getInstance();
Connection conn = null;
try {
conn = instance.getConnection();
} catch (SQLException e1) {
e1.printStackTrace();
}
Statement sta;
if (conn != null) {
try {
int count = 0;
sta = conn.createStatement();
PreparedStatement psta = conn.prepareStatement("UPDATE CLASSES SET IS_ENABLED = ? WHERE CLASS_NAME = ?");
psta.setBoolean(1, false);
psta.setString(2, className);
count = psta.executeUpdate();
System.out.println(count + " record(s) updated.");
psta = conn.prepareStatement("UPDATE CLASSES SET IS_ENABLED = ? WHERE CLASS_NAME = ? AND VERSION = ?");
psta.setBoolean(1, true);
psta.setString(2, className);
psta.setString(3, version);
count = psta.executeUpdate();
System.out.println(count + " record(s) updated.");
sta.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Output
CLASSES Table created
2 record(s) created.
2 record(s) updated.
1 record(s) updated.
Version 1 Greet.getGreetMessage() : Hail to the King Baby!
2 record(s) updated.
1 record(s) updated.
Exception in thread "main" java.lang.LinkageError: loader (instance of com/levent/classloader/DerbyServerClassLoader): attempted duplicate class definition for name: "com/levent/greeter/Greeter"
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(Unknown Source)
at java.lang.ClassLoader.defineClass(Unknown Source)
at com.levent.classloader.DerbyServerClassLoader.findClass(DerbyServerClassLoader.java:38)
at com.levent.example.ClientClassLoaderDBVersionDemo.main(ClientClassLoaderDBVersionDemo.java:43)