Why ClassGraph finds classes from other deployment on Wildfly? - classgraph

I prepared WAR archive and I deployed it on Wildfly 21.
I want to find all classes annotated with javax.ws.rs.ApplicationPath annotation.
try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages( PACKAGE ).scan())
{
final ClassInfoList classesWithAnnotation =
scanResult.getClassesWithAnnotation( ApplicationPath.class.getName() );
}
It finds two classes, one from my deployment and one from other deployment. I undeployed other deployment to make sure that one class is taken from it and I am sure, if only one deployment is deployed on Wildfly then it return only one class.
I do not understand why ClassGraph finds classes from other deployment. How to configure ClassGraph to scan only deployment which ClassGraph itself belongs? I guess that I have to configure class loader, but I do not know how to do it correctly.

Unfortunately it seems that there is no any configuration options that could meet my requirements. Source code modification is needed.
There is JBossClassLoaderHandler#findClasspathOrder method.
First org.jboss.modules.Module is retrieved from class loader. Next getCallerModuleLoader method is invoked and returns different class loader. Original class loader is an instance of org.jboss.modules.ModuleClassLoader class. Class loader retrieved by getCallerModuleLoader method is an instance of org.jboss.as.server.moduleservice.ServiceModuleLoader class. Next moduleMap method is invoked and returns map of modules. This map contains both my deployments. Next operations are executed for each map entry.
I also do not see possibility to add my own implementation of nonapi.io.github.classgraph.classloaderhandler.ClassLoaderHandler without source code modification because handlers are kept in unmodifiable final filed in ClassLoaderHandlerRegistry class.

Related

Using Byte Buddy to expand Spring Boot's classpath

The AWS SDK can locate API call interceptors via classpath scanning (looking for software/amazon/awssdk/global/handlers/execution.interceptors and instantiating classes specified there).
I'm writing a Java Agent with the intention of causing my interceptors to be locatable by the AWS SDK.
My interceptor is bundled with the Java Agent.
My interceptor implements AWS's ExecutionInterceptor.
The AWS SDK is not bundled with my agent, because I'd like the end-user to provide their own AWS SDK version.
For regular standalone applications, this is a no-brainer, as the Java Agent is automatically added to the runtime classpath of the application. The AWS SDK finds my interceptors with no problem.
However, this approach completely breaks with Spring Boot applications where the AWS SDK is bundled as a dependency under BOOT-INF/lib. The reason boils down to Spring Boot's classloading hierarchy. My interceptor class can be found, but its loading fails due to inability to find AWS's ExecutionInterceptor, as it is loaded in a "lower" classloader in the hierarchy.
So I figured that my approach should be to somehow modify Spring Boot's classloader search. However, I'm facing these issues:
At the time of the agent being called, Spring Boot's "lower" classloader isn't created yet.
I am not entirely sure what it is that I need to instrument.
I've read of Byte Buddy being able to help in such "interesting" circumstances but haven't found a way to make this work yet. Any ideas?
(EDIT: I'm looking for a solution that doesn't require code/packaging changes, hence the Java Agent approach)
(EDIT: Things I've tried)
Following Rafael's answer: The method in the SDK that resolves all interceptors is in the class SdkDefaultClientBuilder, and is called resolveExecutionInterceptors.
The following, then, works for standalone JARs which are not SpringBoot applications:
public static void installAgent(Instrumentation inst) {
new AgentBuilder.Default()
.with(RedefinitionStrategy.DISABLED)
.type(ElementMatchers.nameEndsWith("SdkDefaultClientBuilder"))
.transform(
new Transformer() {
#Override
public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription,
ClassLoader classLoader, JavaModule module) {
return builder.visit(Advice.to(MyAdvice.class).on(ElementMatchers.named("resolveExecutionInterceptors")));
}
}
).installOn(inst);
}
For SpringBoot applications, however, it looks like the advice isn't applied at all. I am guessing that this is because the SdkDefaultClientBuilder type isn't even available at the time when the agent starts. It is available during SpringBoot's runtime, in a different classloader.
Byte Buddy allows you to inject code in any method of any class, so the first and only major thing you would need to find out would be where your interceptor is instantiated. This can typically be done by setting a breakpoint in the constructor of the interceptor in the working scenario and investigating the methods in the stack. Find out where the classes are discovered, for example the method where software/amazon/awssdk/global/handlers/execution.interceptors is read.
Once you have identified this method, you would need to find a way to manually extract the interceptors defined by your agent and to manually add them. For example, if the file-extracted interceptors are added to an argument of type List<Interceptor>, you could use Byte Buddy to modify this method to also add those of your agent.
Normally, you use Byte Buddy's AgentBuilder in conjunction with Advice to do so. Advice let's you inline code into another method as for example, assuming you find a method with an argument of type List<Interceptor>:
class MyAdvice {
#Advice.OnMethodEnter
static void enter(#Advice.Argument(0) List<Interceptor> interceptors) {
interceptors.addAll(MyAgent.loadMyInterceptors());
}
}
You can now inline this code into the method in question by:
class MyAgent {
public static void premain(String arg, Instrumentation inst) {
new AgentBuilder.Default().type(...).transform((builder, ...) -> builder
.visit(Advice.to(MyAdvice.class).on(...))).install(inst);
}
}
You might need to use AgentBuilder.Transformer.ForAdvice if the classes in question are not available on the agent's class loader where Byte Buddy resolves the advice using both the target and the agent class loader.

How to create a custom NiFi Controller Service?

I am trying to learn, how to create a custom NiFi controller service. To start off, I thought of mimicking the DBCPConnectionPool controller service by simply copying the original source code of DBCPConnectionPool service. To implement the same, I generated a maven archetype from "nifi-service-bundle-archetype" and got the following project structure
However, when i generated the archetype from 'nifi-processor-bundle-archetype , I got the following structure: -
I understand that in case of processor I simply need to write my code in MyProceesor.java present under nifi-ListDbTableDemo-processors folder and then create a nar file out of it. But in case of controller service, I have 4 folders generated. I can see two java files i.e.
StandardMyService.java present under nifi-DbcpServiceDemo folder
MyService.java present under nifi-DbcpServiceDemo-apifolder
Now, why is there two java files generated in case of custom controller service, while there was only one java file generated in case of custom processor. Also, Since I am trying to mimick the DBCPConnectionPool service, in which java file out of two should I copy the original source code of DBCPConnectionPool service.
Please guide me from scratch, the steps that I need to follow to create a custom service equivalent to that of DBCPConnectionPool service.
MyService.java under nifi-DbcpServiceDemo-api is an interface which be implemented by the StandardMyService.java under nifi-DbcpServiceDemo. Once the implementation is done, you have to use nifi-DbcpServiceDemo-api as dependency in the processor bundle which needs to work with this custom controller Service.
The reason why controller services are implemented this way is:
We will be hiding the actual implementation from the processor bundle because it need not depend on the implementation.
Tomorrow you write a new controller service implementation, say StandardMyServiceTwo which again implements MyService because only the implementation varies from StandardMyService and other members remains the same and can be shared. This new controller service can be introduced transparently without making any changes on the processor bundle.
Example:
The best example is the record reader/writer controller services. If you look at the nifi-record-serialization-services-bundle in nifi, they have different implementation for serializing records of JSON, Grok, avro, CSV data formats but they all are actually implementing one API - nifi-record-serialization-service-api And hence for the processors which want to use the Record Reader or Record Writer, instead of having the actual implementations as its dependency, they rather can have the api as its dependency.
So tomorrow you can add add a new implementation in the record-serialization-services-bundle for a new data format without touching anything on the processors bundle.
For you references, please take a look at the following links which would help you in writing the custom controller service from scratch
http://www.nifi.rocks/developing-a-custom-apache-nifi-controller-service/
https://github.com/bbende/nifi-dependency-example

Class Redefine not working

SO I am trying to redefine a class. I have a class named folder. In OSGi (using Felix) I have a new Folder class with the same methods but some additional logging.
I am trying to take the Folder Class from Felix and redefine the main Folder class on the main classloader
I do have the agent set on startup.
new ByteBuddy()
.redefine(Class.forName(classToOverride.trim()), ClassFileLocator.ForClassLoader.of(felixClassLoader))
.name(classToOverride.trim())
.make() .load(contextClassLoader);
I have tried different strategies in the load method.
Without any strategies I get the following error
Caused by: java.lang.IllegalStateException: Cannot inject already loaded type: class com.dotmarketing.portlets.folders.model.Folder
at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection.inject(ClassInjector.java:187) ~[byte-buddy-1.6.12.jar:?]
at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$Default$InjectionDispatcher.load(ClassLoadingStrategy.java:187) ~[byte-buddy-1.6.12.jar:?]
at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$Default.load(ClassLoadingStrategy.java:120) ~[byte-buddy-1.6.12.jar:?]
at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:79) ~[byte-buddy-1.6.12.jar:?]
at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:4376) ~[byte-buddy-1.6.12.jar:?]
at com.dotmarketing.osgi.GenericBundleActivator.publishBundleServices(GenericBundleActivator.java:177) ~[dotcms_4.1.0_563a5c3.jar:?]
With ClassReloadingStrategy.fromInstalledAgent I get no error but doesn't work.
On a JVM, you cannot simply redefine an already loaded class. You can only redefine a class using a Java agent where Byte Buddy supplies the AgentBuilder API which you can use. Note that it is only possible to change the content of methods but not a class's layout. You probably want to have a look at the Advice API to do so.

Add another UrlHandlerMapping to Spring data rest

I'm having a normal spring-mvc project and I'm also building a rest module as a separate jar file. The goal is when I have the rest jar in my classpath to have the normal website mapped to / and the spring-data-rest repositories mapped to /rest. For the rest module I have defined RepositoryRestMvcConfiguration as well as a WebApplicationInitializer and it all works fine.
So now I want to add some more URLs to the rest module (like /synchronize, and /authenticate, etc.) but as soon as I add controllers in the rest module, they are picked up by the parent application context (the one for the website /). I tried specifying them as bean in the RepositoryRestMvcConfiguration but still they are picked up by the other parent context and the filters of the parent context are fired. And when I access the spring-data-rest through /rest no filters are triggered.
So I was wondering: is there a method I could override in the RepositoryRestMvcConfiguration so that I can add extra url handler mappings?
I assume you mean that you want to have another controller advertised as part of Spring Data REST's root hypermedia.
To do so, you need to create another class in your app like this:
#Component
class DogifierResourceProcessor implements ResourceProcessor<RepositoryLinksResource> {
#Override
public RepositoryLinksResource process(RepositoryLinksResource objects) {
objects.add(new Link(ServletUriComponentsBuilder.fromCurrentRequest()
.build()
.toUriString()
.concat("dogifier/{id}"), "dogifier"));
return objects;
}
}
This will create a hypermedia entry with rel="dogifier" that lists /dogifier/{id} as the URI. It will also prefix it with the proper URN, etc.
Of course, you can use Spring HATEOAS to link to a controller method without having to specify the actual path by hand. That would reduce maintenance and encourage better hypermedia controls.
You need to exclude those controllers from the classpath scanning of the parent context. Just follow the instructions in the Spring documentation.

reading system.servicemodel section from database

We have a dynamically composed application, in which user can add services and operations. This application is installed on a server cluster.
Since adding services to application involves so much writing to web.config, i was wondering if its possible to read system.servicemodel section from a database instead of web.config.
Seems like microsoft's implementation of configuration is very tightly coupled with where its stored.
There is no "out-of-the-box" way to do that. However, it is possible.
Few feet below, Configuration class uses FileStream instance where it actually can use any Stream. That particular step can be replaced with custom implementation of IInternalConfigHost interface (a lot of properties and methods to implement there).
Particularly interesting are OpenStreamForRead and OpenStreamForWrite, both are returning Stream instances. There you can put logic to pull XML of configuration sections from database into ConfigurationSection instances and to put ConfigurationSection instances as XML into database.
The next step is to create an instance of Configuration class. However, here we must get dirty because its constructor never leaves the System.Configuration kingdom. The need to use reflection to reach and use it. I suggest implementation of IInternalConfigConfigurationFactory to wrap the reflection magic.
Configuration Create( Type typeConfigHost,
params object[] hostInitConfigurationParams );
As first parameter pass the type of implemented configuration host.
After we have Configuration instance, we can use it a custom ServiceHost, ChannelFactory<T> and DuplexChannelFactory<T>.