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

任意のコードを実行できるのでセキュリティに注意すること!

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

}

}

おすすめ

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です