📜 ⬆️ ⬇️

Street magic in scripts or what connects Groovy, Ivy and Maven?

After suffering with debugging complex MVEL scripts + MavenClassloader, I discovered that the mechanism for dynamic dependency resolution is in Groovy. In addition, debugging Groovy scripts is possible in both Idea and Eclipse.



You ask why you need dynamic dependency resolution? Some things are easier to do, and some are only possible.
')
In the post you will find a working solution for Groovy in the form of a single jar file and a class loader from the maven repositories for a Java application. Learn about the features of the work of Grape "out of the box" Not to be unfounded and the possibilities of the Grape were clear ...

I will give an example from the official manual:
@Grapes([ @Grab(group='org.eclipse.jetty.aggregate', module='jetty-server', version='8.1.7.v20120910'), @Grab(group='org.eclipse.jetty.aggregate', module='jetty-servlet', version='8.1.7.v20120910'), @Grab(group='javax.servlet', module='javax.servlet-api', version='3.0.1')]) import org.eclipse.jetty.server.Server import org.eclipse.jetty.servlet.* import groovy.servlet.* def runServer(duration) { def server = new Server(8080) def context = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS); context.resourceBase = "." context.addServlet(TemplateServlet, "*.gsp") server.start() sleep duration server.stop() } runServer(10000) 

This script loads the artifacts of the jetty server from the remote repository, adds them to the classpath of the script, creates an instance of the http class of the server, adds a gsp page handler (this is a powerful template mechanism that is in the root), starts the server, waits 10 seconds and stops it. Those. at the time of writing the script, these dependencies are not needed, only access to the repositories is needed and the next time the dependencies are started, the jetty dependencies are already in the local file system and there is no need to download them from the network.

For me, so ingenious mechanism built into the language itself !!!

To run a script with a jetty server, you only need groovy and classes ivy provider in the classpath. Classes runtime loads from the maven repository using ivy.

In the wilds of the grove, a configuration is hidden that says dependencies must first be searched in the local file system $ {user.home} /. Groovy / grapes, then in $ {user.home} /. M2 / repository /, well, then try to find first in jcenter, then in ibiblio, and lastly search in java.net2 repositories
That same configuration
 <ivysettings> <settings defaultResolver="downloadGrapes"/> <resolvers> <chain name="downloadGrapes" returnFirst="true"> <filesystem name="cachedGrapes"> <ivy pattern="${user.home}/.groovy/grapes/[organisation]/[module]/ivy-[revision].xml"/> <artifact pattern="${user.home}/.groovy/grapes/[organisation]/[module]/[type]s/[artifact]-[revision](-[classifier]).[ext]"/> </filesystem> <ibiblio name="localm2" root="file:${user.home}/.m2/repository/" checkmodified="true" changingPattern=".*" changingMatcher="regexp" m2compatible="true"/> <!-- todo add 'endorsed groovy extensions' resolver here --> <ibiblio name="jcenter" root="https://jcenter.bintray.com/" m2compatible="true"/> <ibiblio name="ibiblio" m2compatible="true"/> <ibiblio name="java.net2" root="http://download.java.net/maven/2/" m2compatible="true"/> </chain> </resolvers> </ivysettings> 



But there is one nuance that hinders the wide use of Grape - it is the implementation of its dependency resolution mechanism on Ivy and the absence of provider classes in one jar from the groove. That's what I'm talking about:
igor @ igor-comp: ~ / dev / projects / groovy-grape-aether $ java -jar /home/igor/.m2/repository/org/codehaus/groovy/groovy-all/2.4.5/groovy-all-2.4 .5.jar ~ / dev / projects / jetty.groovy
Caught: java.lang.NoClassDefFoundError: org / apache / ivy / Ivy
java.lang.NoClassDefFoundError: org / apache / ivy / Ivy
Caused by: java.lang.ClassNotFoundException: org.apache.ivy.Ivy


Those who tried to use Ivy with complex transitive dependencies, ranges of versions, or snapshot versions from the maven repositories didn’t get a big shot.

In the Grape.java source code of the groovy project there are such lines
  // by default use GrapeIvy //TODO META-INF/services resolver? instance = (GrapeEngine) Class.forName("groovy.grape.GrapeIvy").newInstance(); 


The search led to the Spring boot project, which uses Grape under the hood, but at the expense of the maven provider implemented on Aether. Aether is a single library for accessing repositories and publishing artifacts. It is used in maven, nexus, m2eclipse. It is unlikely that Ivy will be able to contend with her on the same battlefield. It would be great to use aether in grape!

GrapeEngineInstaller does almost what the groovy authors thought when they wrote a TODO comment - assigns the AetherGrapeEngine provider to the Grape.instance field instead of the hard-coded GrapeIvy.
 public abstract class GrapeEngineInstaller { public static void install(GrapeEngine engine) { synchronized (Grape.class) { try { Field field = Grape.class.getDeclaredField("instance"); field.setAccessible(true); field.set(null, engine); 

And it doesn't matter that the “dirty hack” is implemented in the boot using reflection) The authors' idea of ​​the “TODO META-INF / services resolver?” Is also not the best, especially when the application is modularized and such a resolver will definitely be a pain in the OSGI environment.

For complete happiness, I need AetherGrapeEngine without all the boot and spring classes, and with all the Aether classes necessary for its work.

This led me to the spring boot surgery and isolation surgery, combining AetherGrapeEngine and the mvn- classloader class loaders into a separate artifact of only 3 MB in size. These 3 megabytes will help both the Gruve language and my AspectJ-Scripting project! I am pleased to share the results, I hope that the project will be useful to you.

After combining mvn-classloader and groovy-all, an artifact of 9.8 MB is obtained, which replaces groovy-all and allows you to use the Grape mechanism in your Groovy application using the AetherGrapeEngine dependency resolver.

Download from the central repository groovy-grape-aether-2.4.5.1.jar . It was assembled on the basis of the groovy-grape-aether project.

Initialize ssh server in carash.groovy script:
 @Grab(group='org.crashub', module='crash.connectors.ssh', version='1.3.1') import org.crsh.standalone.Bootstrap import org.crsh.vfs.FS.Builder import org.crsh.vfs.spi.url.ClassPathMountFactory def classLoader = Bootstrap.getClassLoader(); def classpathDriver = new ClassPathMountFactory(classLoader); def cmdFS = new Builder().register("classpath", classpathDriver).mount("classpath:/crash/commands/").build(); def confFS = new Builder().register("classpath", classpathDriver).mount("classpath:/crash/").build(); def bootstrap = new Bootstrap(classLoader, confFS, cmdFS); def config = new java.util.Properties(); config.put("crash.ssh.port", "2000"); config.put("crash.ssh.auth_timeout", "300000"); config.put("crash.ssh.idle_timeout", "300000"); config.put("crash.auth", "simple"); config.put("crash.auth.simple.username", "admin"); config.put("crash.auth.simple.password", "admin"); bootstrap.setConfig(config); bootstrap.bootstrap(); sleep 60000 bootstrap.shutdown(); 


Run this script with the java -jar groovy-grape-aether-2.4.5.1.jar carash.groovy command.
And we see in the console how the script finds the dependency in the repository and works
Nov 05, 2015 1:01:50 AM org.crsh.plugin.PluginLifeCycle configureProperty
INFO: Configuring property vfs.refresh_period = 1 from properties
Nov 05, 2015 1:01:50 AM org.crsh.plugin.ServiceLoaderDiscovery getPlugins
INFO: Loaded plugin Plugin [type = SSHPlugin, interface = SSHPlugin]
Nov 05, 2015 1:01:50 AM org.crsh.plugin.ServiceLoaderDiscovery getPlugins
INFO: Loaded plugin Plugin [type = SSHInlinePlugin, interface = CommandPlugin]
Nov 05, 2015 1:01:50 AM org.crsh.plugin.ServiceLoaderDiscovery getPlugins
INFO: Loaded plugin Plugin [type = KeyAuthenticationPlugin, interface = KeyAuthenticationPlugin]
Nov 05, 2015 1:01:50 AM org.crsh.plugin.ServiceLoaderDiscovery getPlugins
INFO: Loaded plugin Plugin [type = CRaSHShellFactory, interface = ShellFactory]
Nov 05, 2015 1:01:50 AM org.crsh.plugin.ServiceLoaderDiscovery getPlugins
INFO: Loaded plugin Plugin [type = GroovyLanguageProxy, interface = Language]
Nov 05, 2015 1:01:50 AM org.crsh.plugin.ServiceLoaderDiscovery getPlugins
INFO: Loaded plugin Plugin [type = JavaLanguage, interface = Language]
Nov 05, 2015 1:01:50 AM org.crsh.plugin.ServiceLoaderDiscovery getPlugins
INFO: Loaded plugin Plugin [type = ScriptLanguage, interface = Language]
Nov 05, 2015 1:01:50 AM org.crsh.plugin.ServiceLoaderDiscovery getPlugins
INFO: Loaded plugin Plugin [type = JaasAuthenticationPlugin, interface = AuthenticationPlugin]
Nov 05, 2015 1:01:50 AM org.crsh.plugin.ServiceLoaderDiscovery getPlugins
INFO: Loaded plugin Plugin [type = SimpleAuthenticationPlugin, interface = AuthenticationPlugin]
Nov 05, 2015 1:01:50 AM org.crsh.plugin.PluginLifeCycle configureProperty
INFO: Configuring property ssh.port = 2000 from properties
Nov 05, 2015 1:01:50 AM org.crsh.plugin.PluginLifeCycle configureProperty
INFO: Configuring property ssh.auth_timeout = 300000 from properties
Nov 05, 2015 1:01:50 AM org.crsh.plugin.PluginLifeCycle configureProperty
INFO: Configuring property ssh.idle_timeout = 300000 from properties
Nov 05, 2015 1:01:50 AM org.crsh.plugin.PluginLifeCycle configureProperty
INFO: Configuring property ssh.default_encoding = UTF-8 from properties
Nov 05, 2015 1:01:50 AM org.crsh.plugin.PluginLifeCycle configureProperty
INFO: Configuring property auth = simple from properties
Nov 05, 2015 1:01:50 AM org.crsh.plugin.PluginLifeCycle configureProperty
INFO: Configuring property auth.simple.username = admin from properties
Nov 05, 2015 1:01:50 AM org.crsh.plugin.PluginLifeCycle configureProperty
INFO: Configuring property auth.simple.password = admin from properties
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Nov 05, 2015 1:01:50 AM org.crsh.plugin.PluginManager getPlugins
INFO: Initialized plugin Plugin [type = KeyAuthenticationPlugin, interface = KeyAuthenticationPlugin]
Nov 05, 2015 1:01:50 AM org.crsh.plugin.PluginManager getPlugins
INFO: Initialized plugin Plugin [type = JaasAuthenticationPlugin, interface = AuthenticationPlugin]
Nov 05, 2015 1:01:50 AM org.crsh.plugin.PluginManager getPlugins
INFO: Initialized plugin Plugin [type = SimpleAuthenticationPlugin, interface = AuthenticationPlugin]
Nov 05, 2015 1:01:50 AM org.crsh.ssh.SSHPlugin init
INFO: Booting SSHD
Nov 05, 2015 1:01:50 AM org.crsh.plugin.PluginManager getPlugins
INFO: Initialized plugin Plugin [type = GroovyLanguageProxy, interface = Language]
Nov 05, 2015 1:01:50 AM org.crsh.plugin.PluginManager getPlugins
INFO: Initialized plugin Plugin [type = JavaLanguage, interface = Language]
Nov 05, 2015 1:01:50 AM org.crsh.plugin.PluginManager getPlugins
INFO: Initialized plugin Plugin [type = ScriptLanguage, interface = Language]
Nov 05, 2015 1:01:50 AM org.crsh.plugin.PluginManager getPlugins
INFO: Initialized plugin Plugin [type = CRaSHShellFactory, interface = ShellFactory]
Nov 05, 2015 1:01:50 AM org.crsh.ssh.term.SSHLifeCycle init
INFO: About to start CRaSSHD
Nov 05, 2015 1:01:50 AM org.crsh.ssh.term.SSHLifeCycle init
INFO: CRaSSHD started on port 2000
Nov 05, 2015 1:01:50 AM org.crsh.plugin.PluginManager getPlugins
INFO: Initialized plugin Plugin [type = SSHPlugin, interface = SSHPlugin]
Nov 05, 2015 1:01:50 AM org.crsh.plugin.PluginManager getPlugins
INFO: Initialized plugin Plugin [type = SSHInlinePlugin, interface = CommandPlugin]
Nov 05, 2015 1:01:56 AM org.crsh.ssh.SSHPlugin destroy
INFO: Shutting down SSHD


You can verify this by connecting to this server: ssh admin@127.0.0.1 -p 2000



So, we can now use dependencies from the maven repositories in our groovy scripts. All you need is groovy-all-2.4.5 combined with AetherGrapeEngine in the artifact
 <dependency> <groupId>com.github.igor-suhorukov</groupId> <artifactId>groovy-grape-aether</artifactId> <version>2.4.5.1</version> </dependency> 


In the same artifact there is a class loader com.github.igorsuhorukov.smreed.dropship.MavenClassLoader for a java program. So if you can’t use Groovy in a project, then similar functionality with dynamic class loading is also available in the java project. But only for this it will still be more convenient to use
 <dependency> <groupId>com.github.igor-suhorukov</groupId> <artifactId>mvn-classloader</artifactId> <version>1.3</version> </dependency> 

I will give an example of java code to get the org.crsh.standalone.Bootstrap class from the maven repository:
 URLClassLoader sshServerClassloader= MavenClassLoader.forMavenCoordinates("org.crashub:crash.connectors.ssh:1.3.1"); Class<?> bootstrapClass = sshServerClassloader.loadClass("org.crsh.standalone.Bootstrap"); 


To summarize what was said in the article: Grape is a built-in mechanism for dynamic dependency loading from maven repositories. I managed to extract from the spring boot only the part necessary for the GrapeEngine provider, and combine it with Aether and the minimum necessary set of dependencies. Ivy provider with his problems is no longer needed. I will most likely move from MVEL to Groovy in my project. And I wish you successful experiments with Grape and a new degree of freedom and convenience in programming on Groovy.

We found out that Groovy, Ivy, and Maven bind a part of the Grape groove language, a technology for dynamically connecting dependencies, and found out how Grape can be used in your scripts.

Source: https://habr.com/ru/post/270145/


All Articles