JavaWebStartに挑む。

以前に、断念したJavaWebStartからのWebアプリケーション起動が、Winstoneを使う事によって、
超簡単に実現できる事が判明したので、久しぶりに頑張ってみる。


実装は、主にhudsonからパクってきました。


warファイルだけで、自身に含まれているwinstoneごと起動する為のMainクラス。
テンポラリファイルを作って、処理が終わったら、リソースを消そうと頑張っているますが、実は消えません。

public class Main {

	private static final String W = "winstone";

	public static void main(String[] args) throws Exception {
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		URL jar = loader.getResource(W + ".jar");
		URLConnection uc = jar.openConnection();
		uc.setDefaultUseCaches(false);
		if (uc instanceof JarURLConnection) {
			JarURLConnection jarcon = (JarURLConnection) uc;
			File me = new File(jarcon.getJarFile().getName());

			final File tmpJar = File.createTempFile(W, ".jar");
			copy(jar.openStream(), new FileOutputStream(tmpJar));

			ClassLoader cl = new URLClassLoader(new URL[] { tmpJar
					.toURL() });
			Class launcher = cl.loadClass("winstone.Launcher");
			Method main = launcher.getMethod("main",
					new Class[] { java.lang.String[].class });
			List<String> arguments = new ArrayList<String>(Arrays
					.<String> asList(args));
			arguments.add(0, "--warfile=" + me.getAbsolutePath());
			main.invoke(null, new Object[] { arguments
					.toArray(new String[arguments.size()]) });

			Runtime.getRuntime().addShutdownHook(new Thread() {
				public void run() {
					try {
						delete(new File(tmpJar.getParentFile(), W));
						delete(new File(tmpJar.getParentFile(), W + ".tmp"));
						tmpJar.delete();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			});
		} else {
			System.err.println("I'm not jar file...");
		}
	}

	private static void copy(InputStream in, OutputStream out)
			throws IOException {
		byte buf[] = new byte[8192];
		int len;
		try {
			while (0 < (len = in.read(buf))) {
				out.write(buf, 0, len);
			}
		} finally {
			in.close();
			out.close();
		}
	}

	private static void delete(File file) throws IOException {
		if (file.isDirectory()) {
			File files[] = file.listFiles();
			if (files != null) {
				for (int i = 0; i < files.length; i++)
					delete(files[i]);

			}
		}
		file.delete();
	}
}

次に、JavaWebStartで起動した時に、コンソールをリダイレクトする先となるSwingアプリ。
ログを出さずに、起動・停止ボタンを設置するのがイイかもしれないと思ったり。

public class JNLPMain {

	public static void main(String[] args) throws Exception {
		System.setSecurityManager(null);
		UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		(new MainDialog()).setVisible(true);
		Main.main(args);
	}

	static class MainDialog extends JFrame {

		private static final long serialVersionUID = 7528520198276440331L;

		public MainDialog() throws HeadlessException, IOException {
			super("Console");
			JScrollPane pane = new JScrollPane(textArea);
			pane.setMinimumSize(new Dimension(800, 150));
			pane.setPreferredSize(new Dimension(800, 150));
			add(pane);
			setDefaultCloseOperation(3);
			setLocationByPlatform(true);
			PipedOutputStream out = new PipedOutputStream();
			PrintStream pout = new PrintStream(out);
			System.setErr(pout);
			System.setOut(pout);
			final BufferedReader in = new BufferedReader(new InputStreamReader(
					new PipedInputStream(out)));
			(new Thread() {

				public void run() {
					do
						try {
							String line;
							while ((line = in.readLine()) == null)
								;
							final String text = line;
							SwingUtilities.invokeLater(new Runnable() {
								public void run() {
									textArea.append(text + '\n');
									scrollDown();
								}
							});
						} catch (IOException e) {
							throw new Error(e);
						}
					while (true);
				}
			}).start();
			pack();
		}

		private void scrollDown() {
			int pos = textArea.getDocument().getEndPosition().getOffset();
			textArea.getCaret().setDot(pos);
			textArea.requestFocus();
		}

		private final JTextArea textArea = new JTextArea();

	}
}

次に、サイトに配置するJNLPファイル。

ポイントは、ダウンロードされるjarファイルのURLと、
application-descで指定するJNLPMainクラス。
そこだけ、気を付ければ、大体まぁ、何となく動く感じ。

<!-- See http://java.sun.com/j2se/1.5.0/docs/guide/javaws/developersguide/syntax.html for the syntax -->
<jnlp spec="1.0+">

  <information>
    <title>JNLP Example</title>
    <vendor>JNLP project</vendor>
    <homepage href="http://www.seasar.org/"/>
    <offline-allowed/>
  </information>

  <security>
    <all-permissions/>
  </security>

  <resources>
    <j2se version="1.5+"/>
    <jar href="http://localhost:8082/test.jar"/>
  </resources>

  <application-desc main-class="org.seasar.jnlp.JNLPMain" />
</jnlp>

んで、配布するWebサーバが返すMimeTypeを追加するなり。
取りあえず、サーブレットコンテナ上で配布するなら、こんな感じ。
Servlet2.4な事に深い意味はあるません。テキトーに手元にあったweb.xmlをコピーして、中身を削除しただけだから。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         version="2.4">
         <mime-mapping>
         	<extension>jnlp</extension>
         	<mime-type>application/x-java-jnlp-file</mime-type>
         </mime-mapping>
</web-app>

後は、さっきのMainとJNLPMainが入ってるwarファイルを作った上で、署名するです。
やり方は、このへん
俺様証明書でも、動かすだけなら動かせますです。


もしかしたら、Maven2のGoalで、まとめて署名する事が出来るのかしらん…とか思ったり。