Tomcatで動かすWebアプリをプラグインで機能拡張可能にするためにやったこと

ここら辺を参考に。

プラグインのインターフェースを決める

とりあえず適当に決める。
priority付けてるのはプラグインが複数あったときに実行順を決めたかっただけ。

package com.example;

public interface FooPlugin {
	public int getPriority();
	public Bar execute(Bar bar);
}

プラグイン読み込み管理クラスをつくる

最初URLClassLoaderで試してたんだけど、うまくいかないので、RMIClassLoaderに変更。

package com.example;

import java.io.File;
import java.io.IOException;
import java.rmi.RMISecurityManager;
import java.rmi.server.RMIClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Logger;

public class FooPluginManager {
	private static final Logger logger = Logger.getLogger(FooPluginManager.class.getName());
	private ArrayList<FooPlugin> plugins = new ArrayList<FooPlugin>();

	public FooPluginManager(String pluginPath) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
		logger.info(pluginPath);
		if (System.getSecurityManager() == null) {
			System.setSecurityManager(new RMISecurityManager());
		}
		File dir = new File(pluginPath);
		String[] files = dir.list();
		for (int i = 0; i < files.length; i++) {
			if (files[i].toLowerCase().endsWith(".jar")) {
				File jarFile = new File(pluginPath + File.separator + files[i]);
				JarFile jar = new JarFile(jarFile);
				Manifest manifest = jar.getManifest();
				Attributes attribs = manifest.getMainAttributes();
				String className = attribs.getValue("Plugin-Class");
				if (className != null) {
					className = className.trim();
					Class<?> clazz = RMIClassLoader.loadClass(jarFile.toURL(), className);
					logger.info("load... " + className);
					Object obj = clazz.newInstance();
					plugins.add((FooPlugin) obj);
				}
			}
		}
	}

	public ArrayList<FooPlugin> sort() {
		Collections.sort(plugins, new Comparator<FooPlugin>() {
			public int compare(FooPlugin a, FooPlugin b) {
				if (a.getPriority() < b.getPriority()) return -1;
				if (a.getPriority() > b.getPriority()) return  1;
				return a.getClass().getCanonicalName().compareTo(b.getClass().getCanonicalName());
			}
		});
		for (Iterator<FooPlugin> iter = plugins.iterator(); iter.hasNext();) {
			logger.info("sort... " + iter.next().getClass().getCanonicalName());
		}
		return plugins;
	}
}

プラグインを書く

2個くらい書いてみる。

package com.example;

import com.example.Bar;
import com.example.FooPlugin;

public class FooPluginImpl1 implements FooPlugin {
	private static final int priority = 2;

	public int getPriority() {
		return priority;
	}

	public Bar execute(Bar bar) {
		return bar;
	}
}
package com.example;

import com.example.Bar;
import com.example.FooPlugin;

public class FooPluginImpl2 implements FooPlugin {
	private static final int priority = 1;

	public int getPriority() {
		return priority;
	}

	public Bar execute(Bar bar) {
		return bar;
	}
}

マニフェストを書いてjarファイルをつくる

EclipseのExport機能を使ってjarファイルをつくる。(任意のマニフェストを指定できる。)

p1.mf
Manifest-Version: 1.0
Plugin-Class: com.example.FooPluginImpl1
p2.mf
Manifest-Version: 1.0
Plugin-Class: com.example.FooPluginImpl2

つくった二つのjarファイルをどこか適当に配置する。
「C:\jakarta-tomcat-6.0\webapps\foo\WEB-INF\plugins」に置いた。

メインプログラム

とりあえず読み込んでソートだけしてみる。

		FooPluginManager pm = new FooPluginManager("C:\\jakarta-tomcat-6.0\\webapps\\foo\\WEB-INF\\plugins");
		ArrayList<FooPlugin> plugins = pm.sort();

jakarta-tomcat-6.0/conf/catalina.policyの変更

とりあえずゆるゆるで。

// ========== FOO CODE PERMISSIONS =======================================


// These permissions apply to foo
grant codeBase "file:${catalina.home}/webapps/foo/WEB-INF/classes/-" {
        permission java.security.AllPermission;
};

// These permissions apply to plugin
grant codeBase "file:${catalina.home}/webapps/foo/WEB-INF/plugins/-" {
        permission java.security.AllPermission;
};

Tomcat起動引数を追加

デフォルトだとcatalina.policyは使われず、JREのlib/security/java.policyが使われる。-securityオプションを付けるとcatalina.policyを使うようになるとTomcatのドキュメントにあったんだけど、実際に試すとそんなコマンドはないとか言われて困り果てたところ、RE: Simple Security Manager how to ? というのを見つけた。
Configure -> [Java] タブ -> Java Options

-Djava.security.manager
-Djava.security.policy=C:\jakarta-tomcat-6.0\conf\catalina.policy

おわり

ログで動作確認。

2011/01/19 10:54:17 com.example.FooPluginManager <init>
INFO: C:\jakarta-tomcat-6.0\webapps\foo\WEB-INF\plugins
2011/01/19 10:54:17 com.example.FooPluginManager <init>
INFO: load... com.example.FooPluginImpl1
2011/01/19 10:54:17 com.example.FooPluginManager <init>
INFO: load... com.example.FooPluginImpl2
2011/01/19 10:54:17 com.example.FooPluginManager sort
INFO: sort... com.example.FooPluginImpl2
2011/01/19 10:54:17 com.example.FooPluginManager sort
INFO: sort... com.example.FooPluginImpl1