import java.util.zip.ZipEntry;\r
import java.util.zip.ZipInputStream;\r
\r
+/**\r
+ * <p>\r
+ * ファイルを監視し、アーカイブ・ファイルが配置されたときに作業ディレクトリに展開し、イベントを通知します。\r
+ * </p>\r
+ * <p>\r
+ * {@link #monitor()}メソッドの呼び出しでファイルを一回監視することが出来ます。ファイルを定期的に監視する場合、定期的に{@link #monitor()}メソッドを呼び出してください。\r
+ * </p>\r
+ * \r
+ * @author uguu\r
+ */\r
public class Deployer {\r
\r
private List<DeployerListener> listenerList = new ArrayList<DeployerListener>();\r
\r
private FileMonitor fileMonitor;\r
\r
+ /**\r
+ * <p>\r
+ * インスタンスを初期化します。\r
+ * </p>\r
+ * \r
+ * @param deployDirectory\r
+ * アーカイブ・ファイルの格納ディレクトリ。ここにアーカイブ・ファイルを配置すると、{@link Deployer}クラスが認識し、作業ディレクトリに展開します。<br>\r
+ * nullの場合、{@link IllegalArgumentException}例外をスローします。<br>\r
+ * ディレクトリではない場合、{@link IllegalArgumentException}例外をスローします。\r
+ * @param filePattern\r
+ * アーカイブ・ファイルであると認識するファイルのパターン。絶対パスと比較されます。<br>\r
+ * nullの場合、{@link IllegalArgumentException}例外をスローします。\r
+ * @param workDirectory\r
+ * 作業ディレクトリ。アーカイブ・ファイルはここに展開されます。<br>\r
+ * nullの場合、{@link IllegalArgumentException}例外をスローします。<br>\r
+ * ディレクトリではない場合、{@link IllegalArgumentException}例外をスローします。\r
+ */\r
public Deployer(File deployDirectory, Pattern filePattern, File workDirectory) {\r
if (deployDirectory == null) {\r
throw new IllegalArgumentException("deployDirectory is null.");\r
this.fileMonitor.addListener(new FileMonitorListenerImpl());\r
}\r
\r
+ /**\r
+ * <p>\r
+ * アーカイブ・ファイルの配置、配置解除のイベントが通知されるリスナーを追加します。\r
+ * </p>\r
+ * <p>\r
+ * このメソッドはスレッドセーフです。\r
+ * </p>\r
+ * \r
+ * @param listener\r
+ * イベントが通知されるリスナー。<br>\r
+ * nullの場合、{@link IllegalArgumentException}例外をスローします。\r
+ */\r
public void addListener(DeployerListener listener) {\r
if (listener == null) {\r
throw new IllegalArgumentException("listener is null.");\r
}\r
}\r
\r
+ /**\r
+ * <p>\r
+ * 登録されているリスナーを削除します。\r
+ * </p>\r
+ * <p>\r
+ * このメソッドはスレッドセーフです。\r
+ * </p>\r
+ * \r
+ * @param listener\r
+ * 削除するリスナー。\r
+ */\r
public void removeListener(DeployerListener listener) {\r
synchronized (this.listenerList) {\r
this.listenerList.remove(listener);\r
}\r
}\r
\r
+ /**\r
+ * <p>\r
+ * アーカイブ・ファイルを監視し、配置、配置解除を行い、リスナーにイベントを通知します。\r
+ * </p>\r
+ * \r
+ * @throws IOException\r
+ * 入出力エラーが発生した場合。\r
+ */\r
public void monitor() throws IOException {\r
this.fileMonitor.monitor();\r
}\r
while ((zipEntry = zipIn.getNextEntry()) != null) {\r
if (!zipEntry.isDirectory()) {\r
File f = new File(destDir, zipEntry.getName().replace('/', File.separatorChar));\r
+ // イベントを通知します。\r
+ synchronized (this.listenerList) {\r
+ for (DeployerListener l : this.listenerList) {\r
+ l.deployFile(this, file, destDir, f);\r
+ }\r
+ }\r
if (!f.createNewFile()) {\r
throw new IOException("ファイル\"" + f.getAbsolutePath() + "\"の作成に失敗しました。");\r
}\r
} finally {\r
fileOut.close();\r
}\r
- // イベントを通知します。\r
- synchronized (this.listenerList) {\r
- for (DeployerListener l : this.listenerList) {\r
- l.deployFile(this, file, destDir, f);\r
- }\r
- }\r
} else {\r
File dir = new File(destDir, zipEntry.getName().replace('/', File.separatorChar));\r
if (!dir.mkdirs()) {\r
import java.util.ArrayList;\r
import java.util.Enumeration;\r
import java.util.List;\r
-import java.util.Vector;\r
+import java.util.NoSuchElementException;\r
import java.util.jar.JarEntry;\r
import java.util.jar.JarFile;\r
\r
+/**\r
+ * <p>\r
+ * アーカイブ・ファイル配置後のクラスやリソースを利用するクラスローダーです。\r
+ * </p>\r
+ * <p>\r
+ * アーカイブ・ファイルにはクラス・ファイル、リソース、jarファイルを含めることが出来、コンストラクタでディレクトリを指定することでクラスローダーの管理下に置くことができます。例えば、classes/からクラス・ファイル、lib/からjarファイルを検索するように初期化することが出来ます。\r
+ * </p>\r
+ * <p>\r
+ * ホット・デプロイを前提としているので、クラスローダーが読み込んだリソースなどを破棄する{@link #dispose()}メソッドが用意されています。例えば{@link Deployer}クラスと連携する場合、{@link DeployerListener#undeployEnd(Deployer, File)}メソッドの呼び出しでこのクラスローダーを破棄することが出来ます。\r
+ * TODO: dispose()メソッドは実装されていません。一度管理下に置いたリソースを破棄する方法が分からないためです。\r
+ * </p>\r
+ * \r
+ * @author uguu\r
+ */\r
public class DeployerClassLoader extends ClassLoader {\r
\r
private List<DeployerClassLoaderListener> listenerList = new ArrayList<DeployerClassLoaderListener>();\r
\r
private String jarDirectory;\r
\r
- public DeployerClassLoader(File deployDirectory, String classDirectory, String jarDirectory, ClassLoader parent) throws IOException {\r
+ /**\r
+ * <p>\r
+ * {@link DeployerClassLoader}インスタンスを初期化します。\r
+ * </p>\r
+ * \r
+ * @param deployDirectory\r
+ * アーカイブ・ファイルを配置したディレクトリ。このディレクトリ以下からクラス・ファイル、リソース、jarファイルなどを検索します。<br>\r
+ * nullの場合、{@link IllegalArgumentException}例外をスローします。<br>\r
+ * ディレクトリではない場合、{@link IllegalArgumentException}例外をスローします。\r
+ * @param classDirectory\r
+ * クラス・ファイルを検索するディレクトリの名前。<br>\r
+ * nullの場合、{@link IllegalArgumentException}例外をスローします。\r
+ * @param jarDirectory\r
+ * jarファイルを検索するディレクトリの名前。<br>\r
+ * nullの場合、{@link IllegalArgumentException}例外をスローします。\r
+ * @param parent\r
+ * 親クラスローダー。\r
+ */\r
+ public DeployerClassLoader(File deployDirectory, String classDirectory, String jarDirectory, ClassLoader parent) {\r
super(parent);\r
this.initialize(deployDirectory, classDirectory, jarDirectory);\r
}\r
\r
- public DeployerClassLoader(File deployDirectory, String classDirectory, String jarDirectory) throws IOException {\r
+ /**\r
+ * <p>\r
+ * {@link DeployerClassLoader}インスタンスを初期化します。\r
+ * </p>\r
+ * \r
+ * @param deployDirectory\r
+ * アーカイブ・ファイルを配置したディレクトリ。このディレクトリ以下からクラス・ファイル、リソース、jarファイルなどを検索します。<br>\r
+ * nullの場合、{@link IllegalArgumentException}例外をスローします。<br>\r
+ * ディレクトリではない場合、{@link IllegalArgumentException}例外をスローします。\r
+ * @param classDirectory\r
+ * クラス・ファイルを検索するディレクトリの名前。<br>\r
+ * nullの場合、{@link IllegalArgumentException}例外をスローします。\r
+ * @param jarDirectory\r
+ * jarファイルを検索するディレクトリの名前。<br>\r
+ * nullの場合、{@link IllegalArgumentException}例外をスローします。\r
+ */\r
+ public DeployerClassLoader(File deployDirectory, String classDirectory, String jarDirectory) {\r
super();\r
this.initialize(deployDirectory, classDirectory, jarDirectory);\r
}\r
\r
- private void initialize(File deployDirectory, String classDirectory, String jarDirectory) throws IOException {\r
+ /**\r
+ * <p>\r
+ * クラスローダーを初期化します。\r
+ * </p>\r
+ * \r
+ * @param deployDirectory\r
+ * アーカイブ・ファイルを配置したディレクトリ。このディレクトリ以下からクラス・ファイル、リソース、jarファイルなどを検索します。<br>\r
+ * nullの場合、{@link IllegalArgumentException}例外をスローします。<br>\r
+ * ディレクトリではない場合、{@link IllegalArgumentException}例外をスローします。\r
+ * @param classDirectory\r
+ * クラス・ファイルを検索するディレクトリの名前。<br>\r
+ * nullの場合、{@link IllegalArgumentException}例外をスローします。\r
+ * @param jarDirectory\r
+ * jarファイルを検索するディレクトリの名前。<br>\r
+ * nullの場合、{@link IllegalArgumentException}例外をスローします。\r
+ */\r
+ private void initialize(File deployDirectory, String classDirectory, String jarDirectory) {\r
if (deployDirectory == null) {\r
throw new IllegalArgumentException("deployDirectory is null.");\r
}\r
this.jarDirectory = jarDirectory;\r
}\r
\r
+ /**\r
+ * <p>\r
+ * クラスローダーがクラスを操作したときのイベントを受け取るリスナーを追加します。\r
+ * </p>\r
+ * <p>\r
+ * このメソッドはスレッドセーフです。\r
+ * </p>\r
+ * \r
+ * @param listener\r
+ * イベントが通知されるリスナー。<br>\r
+ * nullの場合、{@link IllegalArgumentException}例外をスローします。\r
+ */\r
public void addListener(DeployerClassLoaderListener listener) {\r
if (listener == null) {\r
throw new IllegalArgumentException("listener is null.");\r
}\r
}\r
\r
+ /**\r
+ * <p>\r
+ * 既に登録されているリスナーを削除します。\r
+ * </p>\r
+ * <p>\r
+ * このメソッドはスレッドセーフです。\r
+ * </p>\r
+ * \r
+ * @param listener\r
+ * 削除するリスナー。\r
+ */\r
public void removeListener(DeployerClassLoaderListener listener) {\r
synchronized (this.listenerList) {\r
this.listenerList.remove(listener);\r
}\r
}\r
\r
+ /**\r
+ * <p>\r
+ * クラスローダーが保持している全てのリソースを破棄します。\r
+ * </p>\r
+ * TODO: メソッドは機能しません。\r
+ */\r
public void dispose() {\r
// TODO 定義済みクラスをうまく解放する方法を考える。\r
}\r
\r
+ /**\r
+ * <p>\r
+ * クラスを検索します。\r
+ * </p>\r
+ * <p>\r
+ * このメソッドは、クラスがまだ読み込まれていないときに呼び出されます。呼び出されると、クラス・ディレクトリやjarファイルからクラスを検索し、見つかった場合、クラスとして定義します({@link #defineClass(String, byte[], int, int)}メソッドを呼び出します)。クラスが見つからなかった場合、{@link ClassNotFoundException}例外をスローします。\r
+ * </p>\r
+ * \r
+ * @param name\r
+ * 検索するクラスのFQN。\r
+ * @return 見つかったクラス。\r
+ * @throws ClassNotFoundException\r
+ * クラスが見つからなかった場合。\r
+ */\r
@Override\r
protected Class<?> findClass(String name) throws ClassNotFoundException {\r
-// String resName = "/" + name.replace('.', '/') + ".class";\r
String resName = name.replace('.', '/') + ".class";\r
URL url = this.findResource(resName);\r
if (url == null) {\r
}\r
}\r
\r
+ /**\r
+ * <p>\r
+ * リソースを検索します。\r
+ * </p>\r
+ * <p>\r
+ * まずクラス・ディレクトリから、次にjarディレクトリからファイルを検索します。そして、最初に見つかったファイルのURLを返します。\r
+ * </p>\r
+ * <p>\r
+ * このメソッドは{@link #findResources(String)}メソッドを呼び出し、その最初のURLを返します。このとき、{@link #findResources(String)}メソッドが{@link IOException}例外をスローする可能性があります。{@link IOException}例外がスローされた場合、nullを返します。\r
+ * </p>\r
+ * \r
+ * @param name\r
+ * リソース名。\r
+ * @return リソースのURL。\r
+ */\r
@Override\r
protected URL findResource(String name) {\r
- URL url;\r
- // クラス ディレクトリから探す。\r
+ Enumeration<URL> resources;\r
try {\r
- url = this.findResourceForClassDirectory(new File(this.deployDirectory, this.classDirectory), name);\r
- } catch (MalformedURLException e) {\r
- throw new RuntimeException(e);\r
- }\r
- if (url != null) {\r
- return url;\r
+ resources = this.findResources(name);\r
+ } catch (IOException e) {\r
+ return null;\r
}\r
- // jarディレクトリから探す。\r
- List<File> jarFileList = this.getJarFileList(new File(this.deployDirectory, this.jarDirectory));\r
- for (File jarFile : jarFileList) {\r
- try {\r
- url = this.findResourceForJarFile(jarFile, name);\r
- } catch (IOException e) {\r
- throw new RuntimeException(e);\r
- }\r
- if (url != null) {\r
- return url;\r
- }\r
+ if (resources.hasMoreElements()) {\r
+ return resources.nextElement();\r
+ } else {\r
+ return null;\r
}\r
- return null;\r
}\r
\r
+ /**\r
+ * <p>\r
+ * リソースを検索します。\r
+ * </p>\r
+ * <p>\r
+ * まずクラス・ディレクトリから、次にjarディレクトリからファイルを検索します。そして、見つかった全てのファイルのURLを列挙します。例えば、"foo/bar/boo.txt"がfoo.jarとbar.jarから見つかった場合、これらのURLが列挙されます。\r
+ * </p>\r
+ * \r
+ * @param name\r
+ * リソース名。\r
+ * @return リソースのURLの列挙。\r
+ * @throws IOException\r
+ * 入出力エラーが発生した場合。\r
+ */\r
@Override\r
protected Enumeration<URL> findResources(String name) throws IOException {\r
- Vector<URL> urlVector = new Vector<URL>();\r
- URL url;\r
- // クラス ディレクトリから探す。\r
- try {\r
- url = this.findResourceForClassDirectory(new File(this.deployDirectory, this.classDirectory), name);\r
- } catch (MalformedURLException e) {\r
- throw new RuntimeException(e);\r
+ File classDir = new File(this.deployDirectory, this.classDirectory);\r
+ if (!classDir.exists()) {\r
+ classDir = null;\r
}\r
- if (url != null) {\r
- urlVector.add(url);\r
+ File jarDir = new File(this.deployDirectory, this.jarDirectory);\r
+ if (!jarDir.exists()) {\r
+ jarDir = null;\r
+ }\r
+ return new ResourceEnumeration(classDir, jarDir, name);\r
+ }\r
+\r
+ /**\r
+ * <p>\r
+ * リソースを列挙します。\r
+ * </p>\r
+ * \r
+ * @author uguu\r
+ */\r
+ private class ResourceEnumeration implements Enumeration<URL> {\r
+\r
+ private String resourceName;\r
+\r
+ private File classDir;\r
+\r
+ private List<File> jarFileList;\r
+\r
+ private int currentIndex = -1;\r
+\r
+ /**\r
+ * <p>\r
+ * インスタンスを初期化します。\r
+ * </p>\r
+ * \r
+ * @param classDirectory\r
+ * クラス・ファイルを検索するディレクトリ。<br>\r
+ * このパスが存在し、かつディレクトリではない場合、{@link IllegalArgumentException}例外をスローします。\r
+ * @param jarDirectory\r
+ * jarファイルを検索するディレクトリ。<br>\r
+ * このパスが存在し、かつディレクトリではない場合、{@link IllegalArgumentException}例外をスローします。\r
+ * @param resourceName\r
+ * 列挙するリソースの名前。<br>\r
+ * nullの場合、{@link IllegalArgumentException}例外をスローします。\r
+ */\r
+ public ResourceEnumeration(File classDirectory, File jarDirectory, String resourceName) {\r
+ if (classDirectory != null && !classDirectory.isDirectory()) {\r
+ throw new IllegalArgumentException("classDirectory is not directory.");\r
+ }\r
+ if (jarDirectory != null && !jarDirectory.isDirectory()) {\r
+ throw new IllegalArgumentException("jarDirectory is not directory.");\r
+ }\r
+ if (resourceName == null) {\r
+ throw new IllegalArgumentException("resourceName is null.");\r
+ }\r
+\r
+ this.classDir = classDirectory;\r
+ this.resourceName = resourceName;\r
+\r
+ this.jarFileList = this.getJarFileList(jarDirectory);\r
}\r
- // jarディレクトリから探す。\r
- List<File> jarFileList = this.getJarFileList(new File(this.deployDirectory, this.jarDirectory));\r
- for (File jarFile : jarFileList) {\r
+\r
+ /**\r
+ * <p>\r
+ * 列挙中に次の要素が存在するかどうかを返します。\r
+ * </p>\r
+ * \r
+ * @return 次の要素が存在する場合はtrue、存在しない場合はfalse。\r
+ */\r
+ public boolean hasMoreElements() {\r
try {\r
- url = this.findResourceForJarFile(jarFile, name);\r
+ URL url = this.nextElement(false);\r
+ return (url != null);\r
} catch (IOException e) {\r
throw new RuntimeException(e);\r
}\r
- if (url != null) {\r
- urlVector.add(url);\r
- }\r
}\r
- return urlVector.elements();\r
- }\r
\r
- private URL findResourceForClassDirectory(File classDir, String resourceName) throws MalformedURLException {\r
- String resName = resourceName.replace('/', File.separatorChar);\r
- File resFile = new File(classDir, resName);\r
- if (resFile.exists()) {\r
- URL url = new URL("file:/" + resFile.getAbsolutePath().replace(File.separatorChar, '/'));\r
- return url;\r
- } else {\r
- return null;\r
+ /**\r
+ * <p>\r
+ * 列挙中の次の要素を返します。\r
+ * </p>\r
+ * \r
+ * @return 列挙中の次の要素。<br>\r
+ * 次の要素が存在しない場合は{@link NoSuchElementException}例外をスローします。\r
+ */\r
+ public URL nextElement() {\r
+ try {\r
+ URL url = this.nextElement(true);\r
+ if (url != null) {\r
+ return url;\r
+ } else {\r
+ throw new NoSuchElementException();\r
+ }\r
+ } catch (IOException e) {\r
+ throw new RuntimeException(e);\r
+ }\r
}\r
- }\r
\r
- private List<File> getJarFileList(File jarDir) {\r
- List<File> jarFileList = new ArrayList<File>();\r
+ private URL nextElement(boolean updateIndex) throws IOException {\r
+ int curIdx = this.currentIndex;\r
\r
- File[] files = jarDir.listFiles();\r
- if (files == null) {\r
- return new ArrayList<File>();\r
- }\r
- for (File file : files) {\r
- if (file.isFile()) {\r
- if (file.getName().endsWith(".jar")) {\r
- jarFileList.add(file);\r
+ while (true) {\r
+ if (curIdx == this.jarFileList.size()) {\r
+ return null;\r
}\r
- } else {\r
- jarFileList.addAll(this.getJarFileList(file));\r
+ if (curIdx == -1) {\r
+ URL url = this.findResourceForClassDirectory(this.classDir, this.resourceName);\r
+ if (url != null) {\r
+ if (updateIndex) {\r
+ this.currentIndex = curIdx;\r
+ }\r
+ return url;\r
+ }\r
+ } else {\r
+ File jarFile = this.jarFileList.get(curIdx);\r
+ URL url = this.findResourceForJarFile(jarFile, this.resourceName);\r
+ if (url != null) {\r
+ if (updateIndex) {\r
+ this.currentIndex = curIdx;\r
+ }\r
+ return url;\r
+ }\r
+ }\r
+ curIdx++;\r
}\r
}\r
\r
- return jarFileList;\r
- }\r
+ private List<File> getJarFileList(File dir) {\r
+ List<File> jarFileList = new ArrayList<File>();\r
\r
- private URL findResourceForJarFile(File jarFile, String resourceName) throws IOException {\r
- JarFile jar = new JarFile(jarFile);\r
- try {\r
- JarEntry entry = jar.getJarEntry(resourceName);\r
- if (entry != null) {\r
- URL url = new URL("jar:file:/" + jarFile.getAbsolutePath().replace(File.separatorChar, '/') + "!/" + entry.getName());\r
+ if (dir != null) {\r
+ File[] files = dir.listFiles();\r
+ if (files == null) {\r
+ return new ArrayList<File>();\r
+ }\r
+ for (File file : files) {\r
+ if (file.isFile()) {\r
+ if (file.getName().endsWith(".jar")) {\r
+ jarFileList.add(file);\r
+ }\r
+ } else {\r
+ jarFileList.addAll(this.getJarFileList(file));\r
+ }\r
+ }\r
+ }\r
+\r
+ return jarFileList;\r
+ }\r
+\r
+ private URL findResourceForClassDirectory(File classDir, String resourceName) throws MalformedURLException {\r
+ if (classDir != null) {\r
+ return null;\r
+ }\r
+ String resName = resourceName.replace('/', File.separatorChar);\r
+ File resFile = new File(classDir, resName);\r
+ if (resFile.exists()) {\r
+ URL url = new URL("file:/" + resFile.getAbsolutePath().replace(File.separatorChar, '/'));\r
return url;\r
} else {\r
return null;\r
}\r
- } finally {\r
- jar.close();\r
}\r
+\r
+ private URL findResourceForJarFile(File jarFile, String resourceName) throws IOException {\r
+ JarFile jar = new JarFile(jarFile);\r
+ try {\r
+ JarEntry entry = jar.getJarEntry(resourceName);\r
+ if (entry != null) {\r
+ URL url = new URL("jar:file:/" + jarFile.getAbsolutePath().replace(File.separatorChar, '/') + "!/" + entry.getName());\r
+ return url;\r
+ } else {\r
+ return null;\r
+ }\r
+ } finally {\r
+ jar.close();\r
+ }\r
+ }\r
+\r
}\r
\r
}\r