I have an SPI interface defined in a maven module A. In Module B, which is a spring-boot application, I have defined META-INF/services/<interface-named-file>.
In Module A, I am having this code.
public class NotificationResultPluginProvider {
private static NotificationResultPluginProvider notificationResultPluginProvider;
private NotificationResultPlugin notificationResultPlugin;
private NotificationResultPluginProvider() {
final ServiceLoader<NotificationResultPlugin> loader = ServiceLoader.load(NotificationResultPlugin.class);
final Iterator<NotificationResultPlugin> it = loader.iterator();
if (it.hasNext()) {
notificationResultPlugin = it.next();
}
}
public static synchronized NotificationResultPluginProvider getInstance() {
if (null == notificationResultPluginProvider) {
notificationResultPluginProvider = new NotificationResultPluginProvider();
}
return notificationResultPluginProvider;
}
public NotificationResultPlugin getNotificationResultPlugin() {
return notificationResultPlugin;
}
}
In Module B - spring boot app - I have an implementation of NotificationResultPlugin interface.
Now here is the pickle.
When I run the boot app from intellij, I see that (in below code) it.hasNext() is true and notificationResultPlugin is found. In this case my application is working as expected.
if (it.hasNext()) {
notificationResultPlugin = it.next();
}
But when I run the boot app with CLI, using a command like below (note I am deflating the jar first and then launching)
jar -xf <jar file>;
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=12341 -cp 'BOOT-INF/lib/*:BOOT-INF/classes' <main/class/fqn>;
Then it.hasNext() is false and my implementation in Module B for given SPI is not found. As a result, my application is not working as expected. I am at wits end now.
Other relevant information:
My spring boot app, is not a web app. That is I am not exposing any rest end point. I am simply using spring-kafka with minimal dependencies.
What am I missing ? Any help is appreciated.
Found solution
As they say, mind works best when you are not trying.
ServiceLoader was not able to load the SPI implementation because META-INF/services directory was not in classpath while I am starting the application. Setting the class path correctly did the trick. Ergo, this is the change i needed to do.
-cp 'BOOT-INF/lib/*:BOOT-INF/classes:.'
Notice that extra dot at the end.
Related
I want to group configuration items in Quarkus and use them as a Map. Something along the lines of the next application.properties:
greeting.names = tom,jane
greeting.tom.message = hello
greeting.tom.name = tom
greeting.jane.message = hi
greeting.jane.name = jane
And have them in the application in a Map, like this:
#ConfigProperties
public class GreetingConfig {
private String name;
private String message;
// getters / setters
}
And use them like this:
private Map<String, GreetingConfig> config;
They are now name indexed, but a List would also be fine (and is what I actually need):
greeting.1.message = hello
greeting.1.name = tom
greeting.2.message = hi
greeting.2.name = jane
Any ideas on how to realize this? I have a programmatic solution, but would prefer a solution by annotation only
I've had a similar problem these days.
So I wrote a simple Quarkus extension which helped me with the configuration.
You can use the guides from the Quarkus site: Quarkus - Writing Your Own Extension and Quarkus - Building my first extension,
but basically these are the steps taken in order to create the configuration:
Have some maven multi module project (not the project where the configuration will be consumed).
Execute a similar command form the project directory:
mvn io.quarkus:quarkus-maven-plugin:1.4.2.Final:create-extension -N \
-Dquarkus.artifactIdBase=keycloak-extension \
-Dquarkus.artifactIdPrefix=otaibe-commons-quarkus- \
-Dquarkus.nameBase="Keycloak extension"
This will create 'sub multi module' project there with the following modules: runtume and deployment.
Go to the runtime module and add and annotate your config class. It should be something similar to this class
In runtime module create a producer class which should register the configuration class as CDI bean:
#ApplicationScoped
public class OtaibeKeycloakQuarkusProducer {
private volatile OtaibeKeycloakConfig otaibeKeycloakConfig;
void initialize(OtaibeKeycloakConfig config) {
this.otaibeKeycloakConfig = config;
}
#Singleton
#Produces
public OtaibeKeycloakConfig otaibeKeycloakConfig() {
return otaibeKeycloakConfig;
}
}
Create a Recorder class which should initialize the Producer:
#Recorder
public class OtaibeKeycloakRecorder {
public void initOtaQuarkusProducer(BeanContainer container, OtaibeKeycloakConfig configuration) {
OtaibeKeycloakQuarkusProducer producer = container.instance(OtaibeKeycloakQuarkusProducer.class);
producer.initialize(configuration);
}
}
In deployment module you have a Processor class. Go there and register your Producer to be injectable as CDI bean and your Recorder to initialize it. Add the similar code:
#BuildStep
AdditionalBeanBuildItem beans() {
return AdditionalBeanBuildItem.builder().addBeanClasses(OtaibeKeycloakQuarkusProducer.class).build();
}
#BuildStep
#Record(ExecutionTime.RUNTIME_INIT)
void otaibeKeycloackConfigBuildItem(BeanContainerBuildItem beanContainer,
OtaibeKeycloakConfig otaibeKeycloakConfig,
OtaibeKeycloakRecorder recorder) {
recorder.initOtaQuarkusProducer(beanContainer.getValue(), otaibeKeycloakConfig);
}
You can find my implementation here.
Now, go to your initial project where the configuration will be consumed and add the runtime module as a dependency.
In order to ensure that the configuration is added properly execute the following maven command:
mvn quarkus:generate-config
Now, you can check the file src/main/resources/application.properties.example and verify whether your properties are added there. The property group should start with quarkus. plus the name of your #ConfigRoot annotation. In my case for example it will start with quarkus.otaibe.keycloak
That's it!
Now in Quarkus 2.9, you do it with #ConfigMapping (#ConfigProperties is deprecated).
#StaticInitSafe
#ConfigMapping
public interface GreetingConfigMapping {
Map<Integer, GreetingDetail> greeting();
}
interface GreetingDetail {
String name();
String message();
}
Try it in a #QuarkusTest; #Inject it.
Before modular application organization, I had one main JavaFX application that load custom created multiple libraries for different options and possibilities in main app.
In old way of implementation, I just send new library to update, main application reads all libraries from folder and it works like a charm. But in a modular system, if my application wants to use new modular library that I send, it needs to update its module-info file, apropos I need to send updates for modular library and for main application.
Just imagine, it would be like, chrome need to send browser update for every new plugin that is created. As I can see, with Java modularity system you can't create modular applications.
Is there a way import new module without updating main application or some other way around?
Java has a class for that: ServiceLoader.
If we assume you have a “service provider” interface named PluginProvider, other modules can declare themselves to provide that service by putting this in their respective module-info.java descriptors:
provides com.john.myapp.PluginProvider with com.library.MyProvider;
Your application would then state that it uses that service in its own module-info:
uses com.john.myapp.PluginProvider;
And your application’s code would create a ModuleFinder that looks in the directory (or directories) where you expect those plugin modules to reside, then pass that ModuleFinder to a Configuration which can be used to create a ModuleLayer for the ServiceLoader:
public class PluginLoader {
private final ServiceLoader<PluginProvider> loader;
public PluginLoader() {
Path pluginDir = Paths.get(System.getProperty("user.home"),
".local", "share", "MyApplication", "plugins");
ModuleLayer layer = PluginProvider.class.getModule().getLayer();
layer = layer.defineModulesWithOneLoader(
layer.configuration().resolveAndBind(
ModuleFinder.of(),
ModuleFinder.of(pluginDir),
Collections.emptySet()),
PluginProvider.class.getClassLoader());
loader = ServiceLoader.load(layer, PluginProvider.class);
}
public Stream<PluginProvider> getAll() {
return loader.stream();
}
public void reload() {
loader.reload();
}
}
You might even want to watch the plugin directory for new or removed files:
try (WatchService watch = pluginDir.getFileSystem().newWatchService()) {
pluginDir.register(watch,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.OVERFLOW);
WatchKey key;
while ((key = watch.take()).isValid()) {
loader.reload();
key.reset();
}
}
In this java document:
https://docs.oracle.com/javase/9/docs/api/java/util/ServiceLoader.html
Deploying service providers as modules chapter,it says:
com.example.impl.ExtendedCodecsFactory is a public class that does not implement CodecFactory, but it declares a public static no-args method named "provider" with a return type of CodecFactory.
But the fact is I can't use provides...with to provide a service implementation and it will throw a compile error and runtime error without implement the service.
Is that possible I provide a public static provider method and I can provide a service implementation without define provides...with in module-info file?
Confused,hope someone can help.
As long as the class with the provider method ends up in a module, this works fine. I just created a small demo project showing that:
// MODULE com.example.codec.api
public interface CodecFactory { }
module com.example.codec.api {
exports com.example.codec;
uses com.example.codec.CodecFactory;
}
// MODULE com.example.codec.impl
public class ExtendedCodecsFactory {
public static CodecFactory provider() {
return new CodecFactory() { };
}
}
module com.example.codec.impl {
requires com.example.codec.api;
provides com.example.codec.CodecFactory
with com.example.impl.ExtendedCodecsFactory;
}
To compile:
javac
-d classes/com.example.codec.api
src/com.example.codec.api/module-info.java
src/com.example.codec.api/com/example/codec/CodecFactory.java
javac
-p classes/com.example.codec.api
-d classes/com.example.codec.impl
src/com.example.codec.impl/module-info.java
src/com.example.codec.impl/com/example/impl/ExtendedCodecsFactory.java
If you're trying to create a service provider that does not live in a module, the provider methods won't work. Unfortunately, the documentation is not terribly clear in this regard. The section Deploying service providers on the class path mentions neither provider constructors nor provider methods, in fact it doesn't even mention inheritance.
The closest you get is in the section above that:
Deploying service providers as modules
[...]
A service provider that is deployed as an automatic module on the application module path must have a provider constructor. There is no support for a provider method in this case.
That covers putting plain JARs without module descriptor onto the module path.
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')
I am trying to get gwt-test-utils to work. I set up the project in the following way:
src/main/java : all the java source code
src/test/java : the test source code
src/test/resources : resource files for the tests
I am building my project with gradle and eclipse. Gradle uses these directories correctly by default and I added all three of them as source directories to Eclipse.
I have successfully built and run the project and was able to execute some plain old JUnit tests as well as a GWTTestCase, so I think I set up the project and its dependencies correctly.
Now I wanted to use gwt-test-utils for some more advanced integration tests. To do so I did the following:
Add the gwt-test-utils and gwt-test-utils-csv to my dependencies
gwtTestUtilsVersion = '0.45'
testCompile group:'com.googlecode.gwt-test-utils', name:'gwt-test-utils', version:gwtTestUtilsVersion
testCompile group:'com.googlecode.gwt-test-utils', name:'gwt-test-utils-csv', version:gwtTestUtilsVersion
Add a gwt-test-utils.properties file to the directory src/test/resources/META-INF with the following content:
path/to/my/module = gwt-module
Added a class that extends GwtCsvTest to a package in the src/test/java directory. It is modeled after the second example in HowToWriteCsvScenario from the gwt-test-utils project wiki, replacing occurrence of their example classes with mine. It looks like this
#CsvDirectory(value = "gwtTests")
public class LoginLogoutTest extends GwtCsvTest
{
#Mock
private MainServiceAsync mainService;
private AppController appController = new AppController();
#CsvMethod
public void initApp()
{
appController.onModuleLoad();
}
#Before
public void setup()
{
GwtFinder.registerNodeFinder("myApp", new NodeObjectFinder()
{
#Override
public Object find(Node node)
{
return csvRunner.getNodeValue(appController, node);
}
});
GwtFinder.registerNodeFinder("loginView", new NodeObjectFinder()
{
#Override
public Object find(Node node)
{
return csvRunner.getNodeValue(appController.getRootPresenter().getCurrentlyActiveSubPresenters().iterator().next().getView(), node);
}
});
addGwtCreateHandler(createRemoteServiceCreateHandler());
}
}
added a csv-file for configuring the test to src/test/resources/gwtTests with the following content
start
initApp
assertExist;/loginView/emailTextBox
I tried executing it via the Eclipse's Run As > JUnit Test and indirectly via gradle build (which executes all the test cases, not just this one). Both lead to the same error:
ERROR GwtTreeLogger Unable to find type 'myPackage.client.AppController'
ERROR GwtTreeLogger Hint: Check that the type name 'myPackage.client.AppController' is really what you meant
ERROR GwtTreeLogger Hint: Check that your classpath includes all required source roots
The AppController class is the entry-point configured in the module I configured in gwt-test-utils.properties, which makes me think that configuration works correctly and the rest of the setup (dependencies and all) work as well.
In an earlier version I used the same file as a subclass of GWTTestCase and created an AppController instance in the same way. That worked, so I'm pretty sure the class path is setup correctly to include it as well. I also tried changing it back to the previous version just now and it still works.
I have no clue why the class is not found. Is there anything gwt-test-utils does differently which means I need to specifically set the class path for it? Otherwise it should just work, since both gradle and eclipse know about all the relevant source folders and dependencies.