GAE/J データストアにバイナリコードをアップロードして動的に機能を拡張する
MovableTypeやWordPressのプラグインを管理するような方法で、後から機能を追加できるようにしたい。Pythonならソースコードをアップロードすればできそうな気もするが、javaではコンパイルの作業が必要になる。調べてみると、scalaなどと組み合わせればGAE上でコンパイルできないこともないらしい。しかしGAE標準の機能ではできないため、とりあえずはコンパイル済みのものをアップロードすることにした。
バイナリコードを保存するJDOクラスは例えば次のようにする。
@PersistenceCapable(identityType = IdentityType.APPLICATION,
detachable="true")
public class PluginClassData {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
@Persistent
private String className;
@Persistent
private Blob classData;
...
}
カスタムクラスローダーでは保存したバイナリコードをデータストアから読み込み、クラスを定義して返す。
class PluginClassLoader extends ClassLoader {
private byte loadClassData(String name)[] {
byte[] data = null;
PersistenceManager pm = PMF.get().getPersistenceManager();
pm.setDetachAllOnCommit(true);
try {
Key key = KeyFactory.createKey(
PluginClassData.class.getSimpleName(), name);
PluginClassData e = pm.getObjectById(
PluginClassData.class, key);
data = e.getClassData().getBytes();
} finally {}
return data;
}
public synchronized Class loadClass(String name, boolean resolve)
throws ClassNotFoundException {
try {
return findSystemClass(name);
} catch (Throwable e) {}
try{
byte[] data = loadClassData(name);
Class c = defineClass(null, data, 0, data.length);
if (resolve){
resolveClass(c);
}
return c;
}catch(Exception e){}
throw new ClassNotFoundException();
}
}
プラグインクラスとして、引数の文字列の前後に@をつけて返すFilter1クラスを作成した。
public class Filter1 {
public static String filter(String str){
return "@" + str + "@";
}
}
これをコンパイルし、key=PluginClassData(“Filter1”) としてデータストアに保存する。保存方法は画像などバイナリファイルをアップロードするのと同じなので省略。実際にこのクラスのfilterメソッドを呼び出すには
PluginClassLoader loader = new PluginClassLoader();
String str = "test";
String className = "Filter1";
try {
Class myClass = loader.loadClass(className, true);
Object myObject= myClass.newInstance();
Method myMethod = myClass.getMethod("filter", String.class);
str = (String)myMethod.invoke(myObject, str);
System.out.println(str);
} catch (Exception e) {
e.printStackTrace();
}
任意のコードを実行できるのでセキュリティに注意すること!
private byte loadClassData(String name)[] {
byte[] data = null;
PersistenceManager pm = PMF.get().getPersistenceManager();
pm.setDetachAllOnCommit(true);
try {
Key key = KeyFactory.createKey(PluginClassData.class.getSimpleName(), name);
PluginClassData e = pm.getObjectById(PluginClassData.class, key);
data = e.getClassData();
} finally {}
return data;
}
public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
try {
return findSystemClass(name);
} catch (Throwable e) {}
try{
byte[] data = loadClassData(name);
Class<?> c = defineClass(null, data, 0, data.length);
if (resolve){
resolveClass(c);
}
return c;
}catch(Exception e){}
throw new ClassNotFoundException();
}
}