<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<body link="#355491" alink="#4262a1" vlink="#355491" style="background: #e2e2e2; margin: 0; padding: 20px;">
<div>
        <table cellpadding="0" bgcolor="#FFFFFF" border="0" cellspacing="0" style="border: 1px solid #dadada; margin-bottom: 30px; width: 100%; -moz-border-radius: 6px; -webkit-border-radius: 6px;">
                <tbody>
                        <tr>
                                <td>
                                        <table border="0" cellpadding="0" cellspacing="0" bgcolor="#FFFFFF" style="border: solid 2px #ccc; background: #dadada; width: 100%; -moz-border-radius: 6px; -webkit-border-radius: 6px;">
                                                <tbody>
                                                        <tr>
                                                                <td bgcolor="#000000" valign="middle" height="58px" style="border-bottom: 1px solid #ccc; padding: 20px; -moz-border-radius-topleft: 3px; -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 5px; -webkit-border-top-left-radius: 5px;">
                                                                        <h1 style="color: #333333; font: bold 22px Arial, Helvetica, sans-serif; margin: 0; display: block !important;">
                                                                        <!-- To have a header image/logo replace the name below with your img tag -->
                                                                        <!-- Email clients will render the images when the message is read so any image -->
                                                                        <!-- must be made available on a public server, so that all recipients can load the image. -->
                                                                        <a href="https://community.jboss.org/index.jspa" style="text-decoration: none; color: #E1E1E1">JBoss Community</a></h1>
                                                                </td>
                                                        </tr>
                                                        <tr>
                                                                <td bgcolor="#FFFFFF" style="font: normal 12px Arial, Helvetica, sans-serif; color:#333333; padding: 20px; -moz-border-radius-bottomleft: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 5px; -webkit-border-bottom-left-radius: 5px;"><h3 style="margin: 10px 0 5px; font-size: 17px; font-weight: normal;">
jboss-modules: Module Repository SPI and Extensibility
</h3>
<span style="margin-bottom: 10px;">
created by <a href="https://community.jboss.org/people/ALRubinger">Andrew Rubinger</a> in <i>JBoss AS 7 Development</i> - <a href="https://community.jboss.org/message/772203#772203">View the full discussion</a>
</span>
<hr style="margin: 20px 0; border: none; background-color: #dadada; height: 1px;">
<div class="jive-rendered-content"><p>I have the following use cases I'd like to fulfill:</p><p style="min-height: 8pt; height: 8pt; padding: 0px;"> </p><p>1) Boot JBossAS from a single-JAR distribution</p><p>2) Boot JBossAS from a slim executable JAR, using module libraries located on a remote repository (if not already in a user's local module repository)</p><p>3) Create a modular Arquillian Server Daemon executable JAR, with module root located inside of this JAR under META-INF/modules</p><p style="min-height: 8pt; height: 8pt; padding: 0px;"> </p><p>From my prototyping, all of these are possible using jboss-modules, though the SPI is a bit restrictive when it comes to abstracting out the module repository.  With some improvements to the jboss-modules SPI, we could promote better extensibility.  In particular, I'm finding ModuleLoader difficult to work with.</p><p style="min-height: 8pt; height: 8pt; padding: 0px;"> </p><p>Reasoning:</p><p style="min-height: 8pt; height: 8pt; padding: 0px;"> </p><pre class="jive-pre"><code class="jive-code jive-java"><font color="navy"><b>protected</b></font> <font color="navy"><b>abstract</b></font> ModuleSpec findModule(<font color="navy"><b>final</b></font> ModuleIdentifier moduleIdentifier) <font color="navy"><b>throws</b></font> ModuleLoadException;
</code></pre><p style="min-height: 8pt; height: 8pt; padding: 0px;"> </p><p>Fulfilling this contract is required of ModuleLoader subtypes.  Typically this amounts to finding and parsing a "module.xml" file from the given ModuleIdentifier, but the mechanism for doing this (ModuleXmlParser) is package-private.  It's therefore not very intuitive (or possible) for users outside of jboss-modules to meet the demands of the method signature.</p><p style="min-height: 8pt; height: 8pt; padding: 0px;"> </p><p>David points out that ModuleLoader implementations are instead encouraged to use a delagation mechanism, via something like:</p><p style="min-height: 8pt; height: 8pt; padding: 0px;"> </p><pre class="jive-pre"><code class="jive-code jive-java">@Override
<font color="navy"><b>protected</b></font> Module preloadModule(<font color="navy"><b>final</b></font> ModuleIdentifier identifier) <font color="navy"><b>throws</b></font> ModuleLoadException <font color="navy">{</font>
  <font color="navy"><b>return</b></font> ModuleLoader.preloadModule(identifier, this.delegate);
<font color="navy">}</font>
</code></pre><p style="min-height: 8pt; height: 8pt; padding: 0px;"> </p><p>The issue here is that we need a delegate capable of servicing the request.  LocalModuleLoader is fine for filesystem-based repositories (though it's final and cannot be extended), but modules could conceivably be located anywhere (and may even have some descriptor format other than module.xml).  This is exacerbated by the unavailability of other ModuleLoader implementations which are not visible outside jboss-modules:</p><p style="min-height: 8pt; height: 8pt; padding: 0px;"> </p><p>ClassPathModuleLoader</p><p>JarModuleLoader</p><p style="min-height: 8pt; height: 8pt; padding: 0px;"> </p><p>For for my use cases 1) and 3) above, I believe my only options are to resort to a bit of a hack which unpacks the module root in a JAR to something that can be read by LocalModuleLoader. I'll paste that code below the fold[1].</p><p style="min-height: 8pt; height: 8pt; padding: 0px;"> </p><p><span>Use case 2) I hope to see more discussion on; I have a patch in </span><a class="jive-link-external-small" href="https://issues.jboss.org/browse/MODULES-146:" target="_blank">https://issues.jboss.org/browse/MODULES-146:</a><span> </span><a class="jive-link-external-small" href="https://github.com/jbossas/jboss-modules/pull/22">https://github.com/jbossas/jboss-modules/pull/22</a>.</p><p style="min-height: 8pt; height: 8pt; padding: 0px;"> </p><p>Because we probably don't want to be changing the existing SPI much, perhaps introducing a new ModuleRepository SPI layer would make sense looking forward?  David has mentioned an "install" SPI, but I think "installation" is still an implementation detail with respect to module loading.  I believe an abstraction between jboss-modules and any backing module repository (which is a VFS) will do.</p><p style="min-height: 8pt; height: 8pt; padding: 0px;"> </p><p>S,</p><p>ALR</p><p style="min-height: 8pt; height: 8pt; padding: 0px;"> </p><p>PS - I could even make a ShrinkWrap-based ModuleRepository. <span> :) </span></p><p style="min-height: 8pt; height: 8pt; padding: 0px;"> </p><p>[1] </p><p style="min-height: 8pt; height: 8pt; padding: 0px;"> </p><pre class="jive-pre"><code class="jive-code jive-java"><font color="darkgreen">/*
* JBoss, Home of Professional Open Source
* Copyright 2012, Red Hat Middleware LLC, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/</font>
<font color="navy"><b>package</b></font> org.jboss.arquillian.daemon.main;
 
<font color="navy"><b>import</b></font> java.io.File;
<font color="navy"><b>import</b></font> java.io.FileOutputStream;
<font color="navy"><b>import</b></font> java.io.IOException;
<font color="navy"><b>import</b></font> java.io.InputStream;
<font color="navy"><b>import</b></font> java.io.OutputStream;
<font color="navy"><b>import</b></font> java.security.AccessController;
<font color="navy"><b>import</b></font> java.security.PrivilegedAction;
<font color="navy"><b>import</b></font> java.util.Enumeration;
<font color="navy"><b>import</b></font> java.util.UUID;
<font color="navy"><b>import</b></font> java.util.jar.JarEntry;
<font color="navy"><b>import</b></font> java.util.jar.JarFile;
 
<font color="navy"><b>import</b></font> org.jboss.modules.LocalModuleLoader;
<font color="navy"><b>import</b></font> org.jboss.modules.Module;
<font color="navy"><b>import</b></font> org.jboss.modules.ModuleIdentifier;
<font color="navy"><b>import</b></font> org.jboss.modules.ModuleLoadException;
<font color="navy"><b>import</b></font> org.jboss.modules.ModuleLoader;
<font color="navy"><b>import</b></font> org.jboss.modules.ModuleSpec;
 
<font color="darkgreen">/**
* Hack implmentation of a {@link ModuleLoader} capable of loading modules contained in a JAR under a known module root.
* Explodes the module root into a temporary directory on the filesystem, by which a delegate {@link LocalModuleLoader}
* may then load the modules. Temporarily necessary due to API restrictions in jboss-modules whereby the JarModuleLoader
* is not accessible, nor is parsing a {@link ModuleSpec} from a <code>module.xml</code> file.
*
* @author <a href="mailto:alr@jboss.org">Andrew Lee Rubinger</a>
* @deprecated In place until we can work out proper support for this either in jboss-modules or via a new jboss-modules
*             API which lets us load from a JAR
*/</font>
@Deprecated
<font color="navy"><b>final</b></font> <font color="navy"><b>class</b></font> HackJarModuleLoader <font color="navy"><b>extends</b></font> ModuleLoader <font color="navy">{</font>
 
    <font color="navy"><b>private</b></font> <font color="navy"><b>static</b></font> <font color="navy"><b>final</b></font> String SYSPROP_NAME_TMP_DIR = <font color="red">"java.io.tmpdir"</font>;
    <font color="navy"><b>private</b></font> <font color="navy"><b>static</b></font> <font color="navy"><b>final</b></font> String PREFIX_MODULES_DIR = <font color="red">"modules-"</font>;
 
    <font color="darkgreen">/**
     * Local delegate for loading modules once unpacked
     */</font>
    <font color="navy"><b>private</b></font> <font color="navy"><b>final</b></font> ModuleLoader delegate;
 
    <font color="darkgreen">/**
     * Target location the modules will be unpacked to
     */</font>
    <font color="navy"><b>private</b></font> <font color="navy"><b>final</b></font> File localModulesLocation;
 
    <font color="darkgreen">/**
     * Creates a new {@link ModuleLoader} instance for the specified JAR file, where modules are located in a root
     * denoted by the specified <code>moduleRoot</code> parameter (which is relative to the root of the JAR).
     *
     * @param jar
     * @param moduleRoot
     * @throws IllegalArgumentException
     *             If either argument is not specified
     */</font>
    <font color="navy"><b>public</b></font> HackJarModuleLoader(<font color="navy"><b>final</b></font> JarFile jar, <font color="navy"><b>final</b></font> String moduleRoot) <font color="navy"><b>throws</b></font> IllegalArgumentException <font color="navy">{</font>
 
        <font color="darkgreen">// Precondition checks</font>
        <font color="navy"><b>if</b></font> (jar == <font color="navy"><b>null</b></font>) <font color="navy">{</font>
            <font color="navy"><b>throw</b></font> <font color="navy"><b>new</b></font> IllegalArgumentException(<font color="red">"JAR file must be specified"</font>);
        <font color="navy">}</font>
        <font color="navy"><b>if</b></font> (moduleRoot == <font color="navy"><b>null</b></font> || moduleRoot.length() == 0) <font color="navy">{</font>
            <font color="navy"><b>throw</b></font> <font color="navy"><b>new</b></font> IllegalArgumentException(<font color="red">"Module root within the JAR must be specified"</font>);
        <font color="navy">}</font>
 
        <font color="darkgreen">// Set up the local temp modules directory</font>
        <font color="navy"><b>final</b></font> String tempDirName = SecurityActions.getSystemProperty(SYSPROP_NAME_TMP_DIR);
        <font color="navy"><b>final</b></font> File tempDir = <font color="navy"><b>new</b></font> File(tempDirName);
        <font color="navy"><b>final</b></font> File modulesDir = <font color="navy"><b>new</b></font> File(tempDir, PREFIX_MODULES_DIR + UUID.randomUUID().toString());
        <font color="navy"><b>if</b></font> (!modulesDir.mkdir()) <font color="navy">{</font>
            <font color="navy"><b>throw</b></font> <font color="navy"><b>new</b></font> IllegalStateException(<font color="red">"Could not create modules directory: "</font> + modulesDir.getAbsolutePath());
        <font color="navy">}</font>
 
        <font color="darkgreen">// Explode</font>
        <font color="navy"><b>final</b></font> Enumeration<JarEntry> entries = jar.entries();
        <font color="navy"><b>while</b></font> (entries.hasMoreElements()) <font color="navy">{</font>
            <font color="navy"><b>final</b></font> JarEntry entry = entries.nextElement();
            <font color="navy"><b>if</b></font> (entry.isDirectory()) <font color="navy">{</font>
                <font color="navy"><b>continue</b></font>;
            <font color="navy">}</font>
            <font color="navy"><b>final</b></font> String name = entry.getName();
            <font color="navy"><b>if</b></font> (name.startsWith(moduleRoot)) <font color="navy">{</font>
                <font color="navy"><b>final</b></font> String parsedFullFileName = name.substring(moduleRoot.length() + 1);
                <font color="navy"><b>final</b></font> <font color="navy"><b>int</b></font> lastDirIndex = parsedFullFileName.lastIndexOf(<font color="navy">'/'</font>);
                <font color="navy"><b>if</b></font> (lastDirIndex > 0) <font color="navy">{</font>
                    <font color="navy"><b>final</b></font> String targetRelativeDirName = parsedFullFileName.substring(0, lastDirIndex);
                    <font color="navy"><b>final</b></font> File targetDir = <font color="navy"><b>new</b></font> File(modulesDir, targetRelativeDirName);
                    <font color="navy"><b>if</b></font> (!targetDir.exists()) <font color="navy">{</font>
                        <font color="navy"><b>final</b></font> <font color="navy"><b>boolean</b></font> created = targetDir.mkdirs();
                        <font color="navy"><b>if</b></font> (!created) <font color="navy">{</font>
                            <font color="navy"><b>throw</b></font> <font color="navy"><b>new</b></font> IllegalStateException(<font color="red">"Could not create target directory: "</font> + targetDir);
                        <font color="navy">}</font>
                    <font color="navy">}</font>
                    <font color="navy"><b>final</b></font> String fileName = parsedFullFileName.substring(lastDirIndex);
                    <font color="navy"><b>final</b></font> File targetFile = <font color="navy"><b>new</b></font> File(targetDir, fileName);
                    registerRecursiveDeleteOnExit(targetFile, modulesDir);
                    InputStream in = <font color="navy"><b>null</b></font>;
                    OutputStream out = <font color="navy"><b>null</b></font>;
                    <font color="navy"><b>try</b></font> <font color="navy">{</font>
                        in = jar.getInputStream(entry);
                        out = <font color="navy"><b>new</b></font> FileOutputStream(targetFile);
                        <font color="navy"><b>final</b></font> <font color="navy"><b>byte</b></font>[] buffer = <font color="navy"><b>new</b></font> <font color="navy"><b>byte</b></font>[4096];
                        <font color="navy"><b>int</b></font> read = 0;
                        <font color="navy"><b>while</b></font> ((read = in.read(buffer, 0, buffer.length)) != -1) <font color="navy">{</font>
                            out.write(buffer, 0, read);
                        <font color="navy">}</font>
                    <font color="navy">}</font> <font color="navy"><b>catch</b></font> (<font color="navy"><b>final</b></font> IOException e) <font color="navy">{</font>
                        <font color="navy"><b>throw</b></font> <font color="navy"><b>new</b></font> RuntimeException(<font color="red">"Could not write "</font> + entry.getName() + <font color="red">" to "</font>
                            + targetFile.getAbsolutePath(), e);
                    <font color="navy">}</font> <font color="navy"><b>finally</b></font> <font color="navy">{</font>
                        <font color="navy"><b>if</b></font> (in != <font color="navy"><b>null</b></font>) <font color="navy">{</font>
                            <font color="navy"><b>try</b></font> <font color="navy">{</font>
                                in.close();
                            <font color="navy">}</font> <font color="navy"><b>catch</b></font> (<font color="navy"><b>final</b></font> IOException ioe) <font color="navy">{</font>
                                <font color="darkgreen">// Swallow</font>
                            <font color="navy">}</font>
                        <font color="navy">}</font>
                        <font color="navy"><b>if</b></font> (out != <font color="navy"><b>null</b></font>) <font color="navy">{</font>
                            <font color="navy"><b>try</b></font> <font color="navy">{</font>
                                out.close();
                            <font color="navy">}</font> <font color="navy"><b>catch</b></font> (<font color="navy"><b>final</b></font> IOException ioe) <font color="navy">{</font>
                                <font color="darkgreen">// Swallow</font>
                            <font color="navy">}</font>
                        <font color="navy">}</font>
                    <font color="navy">}</font>
                <font color="navy">}</font>
            <font color="navy">}</font>
        <font color="navy">}</font>
 
        <font color="darkgreen">// Set</font>
        this.delegate = <font color="navy"><b>new</b></font> LocalModuleLoader(<font color="navy"><b>new</b></font> File[] <font color="navy">{</font> modulesDir <font color="navy">}</font>);
        this.localModulesLocation = modulesDir;
 
    <font color="navy">}</font>
 
    <font color="darkgreen">/**
     * {@inheritDoc}
     *
     * @see org.jboss.modules.ModuleLoader#preloadModule(org.jboss.modules.ModuleIdentifier)
     */</font>
    @Override
    <font color="navy"><b>protected</b></font> Module preloadModule(<font color="navy"><b>final</b></font> ModuleIdentifier identifier) <font color="navy"><b>throws</b></font> ModuleLoadException <font color="navy">{</font>
        <font color="navy"><b>assert</b></font> identifier != <font color="navy"><b>null</b></font>;
        <font color="navy"><b>return</b></font> ModuleLoader.preloadModule(identifier, delegate);
    <font color="navy">}</font>
 
    <font color="darkgreen">/**
     * {@inheritDoc}
     *
     * @see org.jboss.modules.ModuleLoader#toString()
     */</font>
    @Override
    <font color="navy"><b>public</b></font> String toString() <font color="navy">{</font>
        <font color="navy"><b>return</b></font> HackJarModuleLoader.class.getSimpleName() + <font color="red">" delegating to modules in "</font>
            + localModulesLocation.getAbsolutePath();
    <font color="navy">}</font>
 
    <font color="darkgreen">/**
     * {@inheritDoc}
     *
     * @see org.jboss.modules.ModuleLoader#findModule(org.jboss.modules.ModuleIdentifier)
     */</font>
    @Override
    <font color="navy"><b>protected</b></font> ModuleSpec findModule(<font color="navy"><b>final</b></font> ModuleIdentifier moduleIdentifier) <font color="navy"><b>throws</b></font> ModuleLoadException <font color="navy">{</font>
        <font color="darkgreen">// Due to incompatible API</font>
        <font color="navy"><b>throw</b></font> <font color="navy"><b>new</b></font> UnsupportedOperationException(<font color="red">"All loading should be done via the delegate in preoadModule"</font>);
    <font color="navy">}</font>
 
    <font color="navy"><b>private</b></font> <font color="navy"><b>static</b></font> <font color="navy"><b>void</b></font> registerRecursiveDeleteOnExit(<font color="navy"><b>final</b></font> File child, <font color="navy"><b>final</b></font> File root) <font color="navy">{</font>
        <font color="navy"><b>if</b></font> (System.getSecurityManager() == <font color="navy"><b>null</b></font>) <font color="navy">{</font>
            child.deleteOnExit();
        <font color="navy">}</font> <font color="navy"><b>else</b></font> <font color="navy">{</font>
            AccessController.doPrivileged(<font color="navy"><b>new</b></font> PrivilegedAction<Void>() <font color="navy">{</font>
                @Override
                <font color="navy"><b>public</b></font> Void run() <font color="navy">{</font>
                    child.deleteOnExit();
                    <font color="navy"><b>return</b></font> <font color="navy"><b>null</b></font>;
                <font color="navy">}</font>
            <font color="navy">}</font>);
        <font color="navy">}</font>
        <font color="navy"><b>final</b></font> File parent = child.getParentFile();
        <font color="navy"><b>if</b></font> (!child.equals(root)) <font color="navy">{</font>
            registerRecursiveDeleteOnExit(parent, root);
        <font color="navy">}</font>
    <font color="navy">}</font>
 
<font color="navy">}</font>
</code></pre></div>
<div style="background-color: #f4f4f4; padding: 10px; margin-top: 20px;">
<p style="margin: 0;">Reply to this message by <a href="https://community.jboss.org/message/772203#772203">going to Community</a></p>
        <p style="margin: 0;">Start a new discussion in JBoss AS 7 Development at <a href="https://community.jboss.org/choose-container!input.jspa?contentType=1&containerType=14&container=2225">Community</a></p>
</div></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>