3

I am using groovy to execute a set of groovy scripts, which works fine when the runtime jar is deployed with the webapp and the runtime executes the groovy scripts present under C:\jboss-eap-7.4\bin (java working directory). Due to portability and other constraints now we need to move these groovy scripts as part of the runtime jar and then load and execute these scripts from the class path.

Can anyone help in running the groovy scripts present inside the runtime jar file (within the webapp)?

enter image description here

The current implementation that executes the groovy scripts from C:\jboss-eap-7.4\bin (java working directory)

** Updated after aswer given by GPT-3**

final String PLUGIN_DESCRIPTOR = "Plugins.groovy"
    final class PluginBootStrapper 
{
    private GroovyScriptEngine scriptEngine = null;
    private GroovyShell shell;
    private GroovyClassLoader classLoader = null;
    private final boolean loadFromClasspath;

    private final List<Plugin> allPlugins = null;
    
    private static final Logger logger = LogManager.getLogger(PluginBootStrapper.class);
    
    public PluginBootStrapper() 
    {
        System.out.println("Inside PluginBootStrapper")
        logger.info("Inside PluginBootStrapper")
        String pluginsDir = System.getProperty(CSMProperties.endorsed_plugins_dir)//".\\plugins"//System.getProperty(CSMProperties.endorsed_plugins_dir)
        loadFromClasspath = true
        shell =  new GroovyShell();
        //scriptEngine = new GroovyScriptEngine(CommonUtils.getAllDirectories(pluginsDir))

        logger.info "Plugins Directory:"+pluginsDir
        println "Plugins Directory:"+pluginsDir
        
        allPlugins = loadDescriptor().invokeMethod("getAllPlugins", null)       
    }
    
    private Object loadDescriptor()
    {
        Object pluginDescriptor = bootStrapScript(CSMProperties.get(CSMProperties.PLUGIN_DESCRIPTOR))                                                                
        pluginDescriptor                         
    }
        
    Object bootStrapScript(String script)
    {
        String pluginsDir = System.getProperty(CSMProperties.endorsed_plugins_dir)
        if (pluginsDir != null) {
            script = pluginsDir + script
        }
        
        printClassPath(this.class.getClassLoader())

        Object pluginScript = null
        //logger.info "script: "+ script
        //String path = this.getClass().getClassLoader().getResource(script).toExternalForm()
        //logger.info "bootStrapScript script "+ script
        logger.info "bootStrapScript script: "+ script + ", path: "+ new File(script).absolutePath
        println "bootStrapScript script: "+ script + ", path: "+ new File(script).absolutePath
        if (this.loadFromClasspath) {
            pluginScript = new GroovyShell(this.class.getClassLoader()).evaluate(new File(script));     //<-- Line no:60
            /* classLoader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader());
            pluginScript = classLoader.parseClass(new File(script)); */
            return pluginScript
        } else {
            pluginScript = scriptEngine.loadScriptByName(script).newInstance()
            return pluginScript
        }
        
        return pluginScript
    }
    
    public List<Plugin> getAllPlugins()
    {
        return allPlugins
    }

    def printClassPath(classLoader) {
        println "$classLoader"
        classLoader.getURLs().each {url->
            println "- ${url.toString()}"
        }
        if (classLoader.parent) {
            printClassPath(classLoader.parent)
        }
    }
    
}

CommonUtils.getAllDirectories method

public static String[] getAllDirectories(String directory)
{
    logger.info("Inside CommonUtils"+directory)
    def dir = new File(directory)
    def dirListing = []
    if (dir.exists()) {
        logger.info "Looking for plugins in "+dir.absolutePath+" directory."
        
        dir.eachFileRecurse {file->
             if(file.isDirectory()) {
                 dirListing << file.getPath()
                 logger.info "Using "+file.getPath()+" plugin folder."
             } else {
                 logger.info "Using "+file.getPath()+" plugin file."
             }
        }
    } else {
        logger.error directory+" folder does not exist. Please provide the plugin files."
    }
    
    dirListing.toArray() as String []
}

Test Run Command: java -cp runtime-2.0-jar-with-dependencies.jar;.runtime-2.0-jar-with-dependencies.jar; -Dendorsed_plugins_dir=runtime-2.0-jar-with-dependencies.jar\ com.Main %*

Output enter image description here

** Old Update**

At present with the above code, if I place the plugins folder (groovy script) inside the jar, it says it cannot find Plugins.groovy under C:\jboss-eap-7.4\bin folder

Screenshot of jar inside war file C:\Users\ricky\Desktop\siperian-mrm.ear\mysupport.war\WEB-INF\lib\runtime.jar\ enter image description here

Ricky
  • 314
  • 1
  • 7
  • 22
  • Sounds like your question is different - how to list resources under particular folder in jar... – daggett Nov 27 '22 at 06:35
  • according to your code you are already inside groovy - why you are using new GroovyScriptEngine to load plugin classes/scripts when it's possible to use `new plugins.desc.Plugins()` to instantiate `/plugins/desc/Plugins.groovy` ? – daggett Nov 29 '22 at 12:43
  • @daggett: This code base is used in two projects: one that is java-based and allows us to change the plugins (groovy scripts) as needed without restarting the servers, and another that requires it to be bundled inside the jar. Hence trying to create a common utility. – Ricky Nov 30 '22 at 05:20
  • but you have groovy runtime in memory. So, question still valid - is there a reason why you are not using existing groovy runtime with simple `new a.b.Plugins()` statement? It could load groovy class even from jar... – daggett Nov 30 '22 at 08:15
  • @daggett The Plugins() in turn contain links to other groovy scripts. So if you see `PluginBootStrapper` constructor it will find all such directories and groovy scripts inside the `plugins` folder and then it will use `scriptEngine` to load instances and of each script and run them. – Ricky Nov 30 '22 at 09:05
  • here is the problem: officially you can't list classes/resources inside jar. you can access them by name. as a workaround you could get jar location using `someClass.getClass().protectionDomain.codeSource.location` then try to list jar content using https://docs.oracle.com/javase/8/docs/api/java/util/zip/ZipFile.html the rest of your code should work but get script content from zipentry or as a resource using class loader or as i said you could just use `currentGroovyClassLoader.loadClass('plugins.desc.Plugins')` - however in this case it should have package declared... – daggett Nov 30 '22 at 21:55
  • @daggett Thank you for your interest in learning more about my problem. Nothing seems to be working for me anymore. So I made a bare-bones project [here](https://github.com/ruchikumhari/groovy-java/tree/main/groovy-script-example), could you please take a look and see if it is possible? – Ricky Dec 02 '22 at 14:29
  • you could not access script by file. this should work for you: `groovyScriptEngine.loadScriptByName('Plugins.groovy')` in case if `Plugins.groovy` located in the root of jar and jar is in classpath. – daggett Dec 02 '22 at 14:48
  • @daggett I used `groovyScriptEngine = new GroovyScriptEngine('.').with { loadScriptByName(script).newInstance() }` and ran the jar file using `java -cp mvn-groovy-test-example-1.0-jar-with-dependencies.jar; com.example.App %*` command. It failed with ResourceException `Exception in thread "main" groovy.util.ResourceException: Cannot open URL: file:/C:/Share/groovy-script-example/target/./, Plugins.groovy` – Ricky Dec 02 '22 at 15:08

2 Answers2

2

let's assume you have plugins.jar with the following groovy file

/a/b/C.groovy

def f(x){
    println "${this.getClass()} :: ${x}"
}

option 1

//in this case you don't need plugins.jar to be in classpath
//you should detect somehow where the plugins.jar is located for current runtime
URL jar = new URL('jar:file:./plugins.jar!/') //jar url must end with !/
def groovyScriptEngine = new GroovyScriptEngine(jar)
def C = groovyScriptEngine.loadScriptByName('a/b/C.groovy')
def c = C.newInstance()
c.f('hello world')

option 2

//plugins.jar must be in current classpath
//C.groovy must have a `package` header if it's located in /a/b folder: package a.b
def C = this.getClass().getClassLoader().loadClass('a.b.C')
def c = C.newInstance()
c.f('hello world')

option 3

//plugins.jar must be in current classpath
def url = this.getClass().getClassLoader().getResource('a/b/C.groovy')
def gshell = new GroovyShell()
def c = gshell.parse(url.toURI())
c.f('hello world')

iterating files in jar

in theory it's possible to list files inside jar however application server could have restrictions on this.

after you got entrypoint - instance of class/script a/b/C.groovy ('Plugins.groovy' in your case), you can get reference to jar and iterate entries:

URL jar = c.getClass().protectionDomain.codeSource.location //you don't need this for option #1

JarURLConnection jarConnection = (JarURLConnection)url.openConnection()
jarConnection.getJarFile().with{jarFile->
    jarFile.entries().each{java.util.jar.JarEntry e->
        println e.getName()
        if(!e.isDirectory()){
            //you can load/run script here instead of printing it
            println '------------------------'
            println jarFile.getInputStream(e).getText()
            println '------------------------'
        }
    }
}
daggett
  • 26,404
  • 3
  • 40
  • 56
1

The answers provided above are also correct, particularly those provided by @daggett, as they include multiple ways to read/access a resource from within a jar file. I'm sharing my version, which has been tested on this codebase.

I made the following changes

  1. Placed all the scripts inside "plugins" folder.

enter image description here

  1. Modified the pom.xml to include the "plugins" folder instead of individual groovy scripts. <include>plugins/</include>

  2. Using getResourceAsStream something like below to read the scripts.

    StringBuilder pluginsDir = new StringBuilder(System.getProperty("plugins_dir").toString()) String script = pluginsDir.append(script) InputStream inputStream = getClass().getResourceAsStream(script) BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)) pluginScript = groovyShell.parse(reader); return pluginScript

  3. Using below command to run

    java -cp mvn-groovy-test-example-1.0-jar-with-dependencies.jar; -Dplugins_dir=/plugins/ com.example.App

enter image description here

AabinGunz
  • 12,109
  • 54
  • 146
  • 218