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();
}
}