Trying to load spring beans from custom groovy file in Grails 2.3.7. I know this question has been asked before, but after hours of searching, I'm unable to find a consistent approach that loads from the classpath.
The Goal
Modularize resources.groovy into multiple custom resource files
Put custom resource files in standard location: grails-app/conf/spring
Use plugin to do the magic; minimize overhead
Tried...
//## grails-app/conf/spring/MyBeansConfig.groovy
beans {
testsvc(TestService){
msg = 'hello'
}
}
Note above, I'm using beans {}, not beans = {}, apparently it makes a difference:
resources.groovy
This works...
beans = {
loadBeans 'file:C:\\Proj\Test1\grails-app\\conf\\spring\\MyBeansConfig.groovy'
}
...and according to docs, this should too, but doesn't:
importBeans("classpath:*MyBeansConfig.groovy")
UPDATE - WORKING OPTIONS
(working for Grails 2.3.7)
Option 1: (src/java)
Following lukelazarovic advice, this approach works since Grails automatically copies (not compiles) groovy files in src/java to the classpath; works fine in eclipse and with war deployment:
//bean config files here
src/java/MyBeansConfig.groovy
src/java/FooBeansConfig.groovy
//resources.groovy
loadBeans('classpath*:*BeansConfig.groovy')
Options 2: (grails-app/conf/spring)
This approach allows for custom bean config files in grails-app/conf/spring (credits to ideascultor and mark.esher)
//bean config files here
grails-app/conf/spring/MyBeansConfig.groovy
//## resources.groovy
//for eclipse/local testing
loadBeans('file:./grails-app/conf/spring/*BeansConfig.groovy')
//for WAR/classpath
loadBeans('classpath*:*BeansConfig.groovy')
//## BuildConfig.groovy
grails.war.resources = { stagingDir, args ->
copy(todir: "${stagingDir}/WEB-INF/classes/spring") {
fileset(dir:"grails-app/conf/spring") {
include(name: "*BeansConfig.groovy")
exclude(name: "resources.groovy")
exclude(name: "resources.xml")
}
}
}
Options 3: (Custom Plugin)
If you're using custom plugins, this approach is ideal; boiler plate config gets refactored into the plugin:
Plugin Config
//## scripts/_Events.groovy
eventCreateWarStart = { warName, stagingDir ->
def libDir = new File("${stagingDir}/WEB-INF/classes/spring")
ant.copy(todir: libDir) {
fileset(dir:"grails-app/conf/spring") {
include(name: "*BeansConfig.groovy")
exclude(name: "resources.groovy")
exclude(name: "resources.xml")
}
}
}
//## MyGrailsPlugin.groovy
def doWithSpring = {
loadBeans('file:./grails-app/conf/spring/*BeansConfig.groovy')
loadBeans('classpath*:*BeansConfig.groovy')
}
Grails App
No config!...just create your bean config files using the *BeansConfig.groovy convention, good to go!
//bean config files here
grails-app/conf/spring/MyBeansConfig.groovy
Update 9/24/2015
Found some issues with option 3:
loading of duplicate resource files
spring resources not configured correctly for test-app
We managed to fix the above issue such that any resource files in grails-app/conf/spring work the same when executing Grails in eclipse, WAR, test-app, etc.
First step: we created a class to have more control over locating and loading resource files; this was necessary as some files were being loaded more than once due to cross-referenced beans.
We're using a plugin to encapsulate this functionality, so this class lives in that plugin:
class CustomResourceLoader {
static loadSpringBeans(BeanBuilder bb){
def files = ['*'] //load all resource files
//match resources using multiple methods
def matchedResourceList = []
files.each {
matchedResourceList +=
getPatternResolvedResources("file:./grails-app/conf/spring/" + it + ".groovy").toList()
matchedResourceList +=
getPathMatchedResources("classpath*:spring/" + it + ".groovy").toList()
}
//eliminate duplicates resource loaded from recursive reference
def resourceMap = [:]
matchedResourceList.each{
if(it) resourceMap.put(it.getFilename(), it)
}
//make resources.groovy first in list
def resourcesFile = resourceMap.remove("resources.groovy")
if(!resourcesFile)
throw new RuntimeException("resources.groovy was not found where expected!")
def resourcesToLoad = [resourcesFile]
resourceMap.each{k,v -> resourcesToLoad << v }
log.debug("Spring resource files about to load: ")
resourcesToLoad.each{ bb.loadBeans(it) }
}
static def getPatternResolvedResources(path){
ResourcePatternResolver resourcePatternResolver =
new PathMatchingResourcePatternResolver();
return resourcePatternResolver.getResources(path);
}
static def getPathMatchedResources(path){
return new PathMatchingResourcePatternResolver().getResources(path)
}
}
Removed BeansConfig.groovy suffix; WAR generation now picks up any resources in grails-app/conf/spring
plugin, _Events.groovy
eventCreateWarStart = { warName, stagingDir ->
def libDir = new File("${stagingDir}/WEB-INF/classes/spring")
ant.copy(todir: libDir) {
fileset(dir:"grails-app/conf/spring") {
include(name: "*.groovy")
exclude(name: "resources.xml")
}
}
}
}
In the plugin definition file, call the loader from doWithSpring(), passing BeanBuilder (the delegate) as the argument (very important):
def doWithSpring = {
CustomResourceLoader.loadSpringBeans(delegate)
}
That's it, there is no requirement on users of the plugin aside from creating custom resource files in grails-app/conf/spring
I had a similar problem just a few days ago, with a groovy configuration file that I added into grails-app/conf. While this works with other resources (they are copied and available on the classpath), the problem with the groovy file was simply that it was compiled and the class file was included, i.e. not the groovy file itself.
I didn't find any good documentation on how this should be done and finally just added it to web-app/WEB-INF/classes. Groovy files placed here will be copied (not compiled) and available on the classpath.
I had the same problem with custom XML files in Grails 2.1.2.
Having XML resources in grails-app/conf/spring didn't work in production environment AFAIR.
To make it working both in development and production environments I finally put the resources into src/java. I think you can achieve the same result by putting your groovy files into src/groovy.
We can import beans from different groovy/xml file in the following way too : -
use the following in resources.groovy -
importBeans 'file:camel-beans.groovy'
OR
importBeans('classpath:/camel-config.xml')
Place camel-beans.groovy along with resources.groovy and provide package as "package spring" for first case, otherwise put it in web app classpath and use the second way to do it.
If your resources.groovy is at following path
grails-app/conf/spring/resources.groovy
and your camel-beans.groovy is at following path
grails-app/conf/spring/camel-beans.groovy
then you can reference camel-beans.groovy in resources.groovy file by adding following line in resources.groovy
importBeans('file:**/camel-beans.groovy')
Related
I created a base image for a Java application using Jib which I want to extend using Jib.
(The Java application provides extensibility by loading additional Jars from the classpath)
In the extending gradle project, I did this:
jib {
....
container {
entrypoint = 'INHERIT'
}
...
}
It allowed me to reuse the entrypoint and args attributes added in the base image but I also want to extend/reuse the base classpath file.
As Jib creates /app/jib-classpath-file in the extending gradle project, the base layer /app/jib-classpath-file is not visible ( I would assume).
To workaround the issue, I added this in extending container configuration block.
extraClasspath = ['/app/libs/*']
Is there an idiomatic way of achieving this in Jib? One option I was thinking is to specify unique classpath files in base and extending projects and
use them like this in the Java command line:
java -cp #/app/jib-BASE-classpath-file #/app/jib-EXTENDED-classpath-file, but I am not finding the option of specifying the classpath file.
What is the recommended way? Thanks
Not a total solution but this shows how to remove the jvm args layer from the child image build altogether.
//build.gradle.kts
buildscript {
dependencies {
classpath("com.google.cloud.tools:jib-layer-filter-extension-gradle:0.1.0")
}
}
...
jib {
...
pluginExtensions {
pluginExtension {
implementation = "com.google.cloud.tools.jib.gradle.extension.layerfilter.JibLayerFilterExtension"
configuration (Action<com.google.cloud.tools.jib.gradle.extension.layerfilter.Configuration> {
filters { filter { glob ="**/jib-*-file" } }
})
}
}
}
This filters out files added in any given layer of a given jib build. If all the files from a layer are filtered out as in this case then the layer is never added to the image.
Sadly this does not let you see the contents of the java-classpath-file in the base image, which you would need to be able to extend it.
References:
https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin#jib-extensions
https://github.com/GoogleContainerTools/jib-extensions/tree/master/first-party/jib-layer-filter-extension-gradle
https://github.com/GoogleContainerTools/jib-extensions
I've created a Gradle plugin which scans the class files in my project and find which ones have an annotation.
I got it to work and compiled it. Now when I run the task in my project, instead of scanning the classes of the project that I'm in and runs the task on it, it scans the class files of the plugin project itself. What am I doing wrong?
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
try {
ClassPath classpath = ClassPath.from(loader); // scans the class path used by classloader
log.info("classPath = {}", classpath);
for (ClassPath.ClassInfo classInfo : classpath.getTopLevelClassesRecursive(packageName)) {
log.info("classInfo={}", classInfo);
Class<?> clazz = classInfo.load();
if (clazz.isAnnotationPresent(RestController.class)) {
Method[] methods = clazz.getMethods();
for (Method method : methods) {
doSomething(clazz, method);
}
}
}
} catch (IOException | NoSuchFieldException | IllegalAccessException ex) {
String errorMsg = new StringBuilder("Unable to generate ").append(propertiesFileName).append(" file.").toString();
log.error(errorMsg, ex);
throw ex;
}
This cannot work the way you expect:
Your project class files are, by default, not loaded by Gradle as part of building your project. They will be produced by compilation, loaded by test execution, etc ... but normally not as part of Gradle executing tasks.
When your plugin reaches out to classloaders, it is in the managed Gradle world, which has its own tricks with classloaders. So you would have to understand which tricks are pulled ... but given 1, that would still not work.
Given that what you are attempting looks a lot like annotation processing, you should investigate that way of handling your needs.
It took a lot of digging in Gradle forums, and still, I'm experiencing many issues with that, but this is the best solution I could found:
URL[] urls;
List<URL> listOfURL = new ArrayList<>();
SourceSetContainer ssc = getProject().getConvention().getPlugin(JavaPluginConvention.class).getSourceSets();
File classesDir = ssc.getByName("main").getOutput().getClassesDir();
listOfURL.add(classesDir.toURI().toURL());
urls = listOfURL.toArray(new URL[0]);
final ClassLoader loader = new URLClassLoader(urls);
When I start Spring Boot application with Spring-Devtools enabled and classes generated from the WSDL schema I get:
Caused by: java.lang.IllegalArgumentException: org.wsdl.WsdlServiceWs referenced from a method is not visible from class loader
I have a project based on Spring Boot with some of the classes generated from the WSDL file using the org.apache.cxf:cxf-codegen-plugin plugin. Generated classes are stored in target/generated/wsdl/** directory. The name of the package of generated classes differs from the project package name.
I tried several exclusions following the documentation:
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using-boot-devtools-restart-exclude
But all my attempts failed.
restart.exclude.wsdl=target/generated/wsdl
restart.exclude.wsdl=org.wsdl.*
restart.exclude.wsdl=**WsdlServiceWs.class
I want to have Spring-Devtools enabled, having org.wsdl.** generated classes excluded from the restart cycle.
The problem was, that I tried to use the WsdlServiceWs which was in fact an interface returned by WsdlServiceWsService. I had the WsdlServiceWs interface returned as a bean in the configuration:
...
#Bean
public WsdlServiceWs wsdlService() {
return new WsdlServiceWsService().getService();
}
...
I have not thought that this will be the problem. Simply changing the bean to the following:
...
#Bean
public WsdlServiceWsService wsdlService() {
return new WsdlServiceWsService();
}
...
Did the work.
Edit:
This solution only moved the invocation of exception from the Bean creation phase to the execution phase. The issue is still not resolved.
You Can't
because the devtools only check the class parent path, not every folder, you can add an breakpoint on ChangeableUrls.java:59
private ChangeableUrls(URL... urls) {
DevToolsSettings settings = DevToolsSettings.get();
List<URL> reloadableUrls = new ArrayList<>(urls.length);
for (URL url : urls) {
if ((settings.isRestartInclude(url) || isDirectoryUrl(url.toString())) && !settings.isRestartExclude(url)) {
reloadableUrls.add(url);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Matching URLs for reloading : " + reloadableUrls);
}
this.urls = Collections.unmodifiableList(reloadableUrls);
}
you can see the url is file:/xxx/target/classes/
so, you can't exclude one class by this way
I want to implement a mode specific configuration load in Play Framework 2.3.8 using Java. I know that this is achievable in Scala:
import java.io.File
import play.api._
import com.typesafe.config.ConfigFactory
object Global extends GlobalSettings {
override def onLoadConfig(config: Configuration, path: File, classloader: ClassLoader, mode: Mode.Mode): Configuration = {
val modeSpecificConfig = config ++ Configuration(ConfigFactory.load(s"application.${mode.toString.toLowerCase}.conf"))
super.onLoadConfig(modeSpecificConfig, path, classloader, mode)
}
}
Currently I managed to implement the same in java but I cannot cast the typesafe-s ConfigFactory.load(modeSpecificConfigFile) return object from com.typesafe.config.Config to play.Configuration:
#Override
public Configuration onLoadConfig(Configuration configuration, File file, ClassLoader classLoader, Mode mode) {
String modeSpecificConfigFile = "application." + mode.toString().toLowerCase() + ".conf";
Logger.debug("Loading mode specific configuration from: " + modeSpecificConfigFile);
Configuration modeSpecificConfig = ConfigFactory.load(modeSpecificConfigFile);
return super.onLoadConfig(modeSpecificConfig , file, classLoader);
}
Thank you!
Wrap it into play.Configuration:
Configuration modeSpecificConfig = new play.Configuration(ConfigFactory.load(modeSpecificConfigFile));
This solution doesn't work in Play 2.4.6, because as it says in docs :
onLoadConfig Deprecated. This method does not do anything. Instead, specify configuration in your config file or make your own ApplicationLoader (see GuiceApplicationBuilder.loadConfig).
Thus I've implemented a simpler alternative solution, a custom Configuration loader, sharing sample code below.
First, assuming you already have all your parameters in "application.conf"
then create "application.prod.conf" for PROD environment
and (optionally) create "application.dev.conf" for DEV environment
Sample application.prod.conf
# include the main config file, aka make all common params usable
include "application.conf"
# PROD specific params, you can also override params from the included conf
param.for.prod.1 = "etc etc"
here.override.for.prod = "blabla"
usable across your app : ConfigManager.java
public class ConfigManager {
public static play.Configuration modeSpecificConfig = new play.Configuration(
ConfigFactory.load(
"application." + Play.mode().toString().toLowerCase() + ".conf"
)
);
}
Now environment specific params are accessible across your app, you can get any param by using ConfigManager, for example:
ConfigManager.modeSpecificConfig.getString("keyname");
ConfigManager.modeSpecificConfig.getInt("keyname2");
I need to read in a .txt file into a groovy class in order to interrogate it line by line. But I am not sure what folder I put it into in my grails app, and how to get the path to it?
So far I have tried placing it under src and also in a new folder web-app/txt
and I have tried the the following to read it in
fileIn = new File('/lexicon.txt').text
and
fileIn = new File('txt/lexicon.txt').text
to no avail.
Any body have any pointers?
Grails is a Java Web Application, so it will be compiled into a sigle file .war, with all files/classes/etc inside. Most Web containers do unpack war, but there are no any guaranteee, so it's not a good idea to use File to access this file as a file.
Btw, you can place your file into grails-app/conf, at this case it will be placed into classpath, and you'll be able to access it by using:
InputStream lexicon = this.class.classLoader.getResourceAsStream('lexicon.txt')
You could also put this file into a subdirectory, like grails-app/conf/data and load it as ***.getResourceAsStream('data/lexicon.txt')
You can put your file under web-app/
Example:
web-app/lexicon.txt
And then in your controller or service use grailsApplication:
class MyService {
def grailsApplication
public myMethod() {
File myFile = grailsApplication.mainContext.getResource("lexicon.txt").file
}
}
Hope this helps
You can use Spring's resource loading to access the file. With this method you can access the file from a Spring bean, which means Grails can autowire the resource in to its artifacts.
See below for the following steps examples
Place the file in grails-app/conf/.
Make a resource holder class in src/groovy
Add the resource holder as a Spring bean in grails-app/spring/resources.groovy
Then autowire and use the resource wherever you need it
Step 2:
package resource
import org.springframework.core.io.Resource
class ResourceHolder {
Resource lexicon
}
Step 3:
beans = {
lexiconHolder(resource.ResourceHolder) {
lexicon = 'classpath:lexicon.txt'
}
}
Step 4:
class AnyGrailsService {
def lexiconHolder
void aMethodUsingTheLexicon() {
File lexicon = lexiconHolder.lexicon.file
/* Do stuff with the lexicon */
}
In Grails 2, you can use the Grails Resource Locator
class MyService {
def grailsResourceLocator
myMethod() {
def fileIn = grailsResourceLocator.findResourceForURI('/txt/lexicon.txt').file
}
}
Handy tip: to mock this in Spock, use GroovyPageStaticResourceLoader
#TestFor(MyService)
class MyServiceSpec extends Specification {
def setup() {
service.grailsResourceLocator = Mock(GroovyPageStaticResourceLocator)
}
}