bytebuddy apply advice to interface methods - specifically spring data jpa - byte-buddy

I am trying to LOG all methods that are invoked in my Springboot application using byte-buddy based java agent.
I am able to log all layers except Spring data JPA repositories, which are actually interfaces. Below is agent initialization:
new AgentBuilder.Default()
.type(ElementMatchers.hasSuperType(nameContains("com.soka.tracker.repository").and(ElementMatchers.isInterface())))
.transform(new AgentBuilder.Transformer.ForAdvice()
.include(TestAgent.class.getClassLoader())
.advice(ElementMatchers.any(), "com.testaware.MyAdvice"))
.installOn(instrumentation);
any hints or workaround that I can use to log when my repository methods are invoked. Below is a sample repository in question:
package com.soka.tracker.repository;
.....
#Repository
public interface GeocodeRepository extends JpaRepository<Geocodes, Integer> {
Optional<Geocodes> findByaddress(String currAddress);
}
Modified agent:
new AgentBuilder.Default()
.ignore(new AgentBuilder.RawMatcher.ForElementMatchers(any(), isBootstrapClassLoader().or(isExtensionClassLoader())))
.ignore(new AgentBuilder.RawMatcher.ForElementMatchers(nameStartsWith("net.bytebuddy.")
.and(not(ElementMatchers.nameStartsWith(NamingStrategy.SuffixingRandom.BYTE_BUDDY_RENAME_PACKAGE + ".")))
.or(nameStartsWith("sun.reflect."))))
.type(ElementMatchers.nameContains("soka"))
.transform(new AgentBuilder.Transformer.ForAdvice()
.include(TestAgent.class.getClassLoader())
.advice(any(), "com.testaware.MyAdvice"))
//.with(AgentBuilder.Listener.StreamWriting.toSystemOut())
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
.installOn(instrumentation);
I see my advice around controller and service layers - JPA repository layer is not getting logged.

By default, Byte Buddy ignores synthetic types in its agent. I assume that Spring's repository classes are marked as such and therefore not processed.
You can set a custom ignore matcher by using the AgentBuilder DSL. By default, the following ignore matcher is set to ignore system classes and Byte Buddy's own types:
new RawMatcher.Disjunction(
new RawMatcher.ForElementMatchers(any(), isBootstrapClassLoader().or(isExtensionClassLoader())),
new RawMatcher.ForElementMatchers(nameStartsWith("net.bytebuddy.")
.and(not(ElementMatchers.nameStartsWith(NamingStrategy.SuffixingRandom.BYTE_BUDDY_RENAME_PACKAGE + ".")))
.or(nameStartsWith("sun.reflect."))
.<TypeDescription>or(isSynthetic())))
You would probably need to remove the last condition.

for anybody visiting this question / problem - I was able to go around the actual problem with logging actual queries invoked during execution - Bytebuddy is awesome and very powerful - for ex- in my case I am simply advice'ing on my db connection pool classes and gathering all required telemetry -
.or(ElementMatchers.nameContains("com.zaxxer.hikari.pool.HikariProxyConnection"))

Related

Using Hibernate Reactive Panache with SDKs that switch thread

I'm using Reactive Panache for Postgresql. I need to take an application level lock(redis), inside which I need to perform certain operations. However, panache library throws the following error:
java.lang.IllegalStateException: HR000069: Detected use of the reactive Session from a different Thread than the one which was used to open the reactive Session - this suggests an invalid integration; original thread [222]: 'vert.x-eventloop-thread-3' current Thread [154]: 'vert.x-eventloop-thread-2'
My code looks something like this:
redissonClient.getLock("lock").lock(this.leaseTimeInMillis, TimeUnit.MILLISECONDS, this.lockId)
.chain(() -> return Panache.withTransaction(() -> Uni.createFrom.nullItem())
.eventually(lock::release);
)
Solutions such as the ones mentioned in this issue show the correct use with AWS SDK but not when used in conjunction with something like redisson. Does anyone have this working with redisson?
Update:
I tried the following on lock acquire and release:
.runSubscriptionOn(MutinyHelper.executor(Vertx.currentContext())
This fails with the following error even though I have quarkus-vertx dependency added:
Cannot invoke "io.vertx.core.Context.runOnContext(io.vertx.core.Handler)" because "context" is null
Panache might not be the best choice in this case.
I would try using Hibernate Reactive directly:
#Inject
Mutiny.SessionFactory factory;
...
redissonClient.getLock("lock")
.lock(this.leaseTimeInMillis,TimeUnit.MILLISECONDS, this.lockId)
.chain(() -> factory.withTransaction(session -> Uni.createFrom.nullItem())
.eventually(lock::release))

Spring streamBridge create rabbitmq queue on startup

Since spring deprecated org.springframework.cloud.stream.annotation.Output annotation.
I'm using streamBridge new api.
And I wonder what is the best way to auto create the queue automatically on startup like the behaviour of the annotation.
I found a workaround using spring.cloud.stream.function.definition=myChannel just to create the queue
As in this sample
#Bean
fun myChannel(): Supplier<Flux<Message<String>>> = Supplier {
Flux.empty()
}
and application.properties:
bindings:
myChannel-out-0:
destination: Mystuff
producer:
required-groups: mychannel
When I was using #Output annotation the queue was created automatically.
Is there another more elegant solution ?
You still don't need to do that (pre-create the queue), since once you execute the first streamBridge.send the destination will be resolved (queue will be created) and your properties applied.
That said, if you still want to do it you can use spring.cloud.stream.source property and point to the name of the destination that you would identify with #Output in the older versions. For example spring.cloud.stream.source=foo.

How to add ComponentScan.Filter in #EnableEntityDefinedRegion

When I added an includeFilter to #EnableEntityDefinedRegion, it still scanned the whole entity package and created all Region beans. How do I scan the specific Region class? For example, only "Address" Region.
package org.test.entity
#Getter
#Setter
#Region("Address")
public class GfAddress implements Serializable
package org.test.entity
#Getter
#Setter
#Region("CreditCard")
public class GfCreditCard implements Serializable
package org.test.package
public interface IAddressRepository extends GemfireRepository<GfAddress, String>
package org.test.package
public interface ICreditCardRepository extends GemfireRepository<GfCreditCard , String>
#Service
#ClientCacheApplication
#EnableGemfireRepositories(basePackages = IAddressRepository.class, includeFilters = #ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes=AddressRepository.class))
#EnableEntityDefinedRegion(basePackages = GfAddress.class, includeFilters = #ComponentScan.Filter(type = FilterType.REGEX, pattern="GfAddress*"))
public class AddressDataAccess
When I print all the beans that are loaded, I found out that the following beans are created.
Address
CreditCard
IAddressRepository
AddressDataAccess
Version
GemFire : 9.8.6
spring-data-gemfire : 2.1.0
Spring Boot : 2.1.0
Sorry for the delay.
First, have a look at the SDG JIRA ticket I filed, DATAGEODE-352 - "EnableEntityDefinedRegions.includeFilters are inappropriately overridden by framework provided include filters".
In this ticket, I describe a couple of workarounds to this bug (!) in the comments, starting here.
I'd also be careful about your REGEX. I am no Regular Expression expert, but I am certain "GfAddress*" will not properly match the application entity type you are searching for and trying to match even when you pick up the new SDG bits resolving the issue I filed.
I created a similar test, using REGEX, to verify the resolution of the issue, here. This is the REGEX I specified. Using "Programmer*" did not work, as I suspected! That is because the REGEX is not valid and does not match the FQCN as used in the Spring RegexPatternTypeFilter.
Technically, it would be better to be a bit more specific about your type matching and use a "ASSIGNABLE_TYPE" TypeFilter instead, as this test demonstrates.
Finally, while SDG 2.1.x is compatible with GemFire 9.8.x, SD[G] Lovelace, or 2.1.x (e.g. 2.1.18.RELEASE), is officially based on, and only "supports", VMware GemFire 9.5.x (currently 9.5.4).
SDG 2.2.x is officially based on, and "supports", VMware GemFire 9.8.x (currently at 9.8.8).
You can review the new SDG Version Compatibility Matrix for more details.
If you have more questions, please follow up here or in DATAGEODE-352.

Error while setting up Spring Data JPA Read-Only Repository

Spring Boot: 1.3.0.RC1
Spring Boot Starter JPA: 1.3.0.RC1
Having an issue with setting up a Spring Data JPA Read Only Repository.
#NoRepositoryBean
public interface ReadOnlyRepository<T, ID extends Serializable> extends Repository<T, ID> {
T findOne(ID id);
Iterable<T> findAll();
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
Using IntelliJ 15 I am getting this compile error:
Error:(16, 83) java: type org.springframework.stereotype.Repository does not take parameters
The error points at this bit of code: Repository<T, ID>
Has something changed within Spring Data JPA? Am I doing something wrong?
Following examples as listed here: Fine-tuning Spring Data repositories
The error points in the right direction. IntelliJ 15 when using ctrl space pulls the Repository Stereotype import org.springframework.stereotype.Repository rather than the correct import org.springframework.data.repository.
If you type quickly and don't notice the wrong import you will receive the error above.

JAX-RS return a Map<String,String>

I want to retrieve a Map from a using JAX-RS (text/xml)
#GET
public Map<String,String> getMap(){
}
but I am getting the error below:
0000001e FlushResultHa E org.apache.wink.server.internal.handlers.FlushResultHandler handleResponse The system could not find a javax.ws.rs.ext.MessageBodyWriter or a DataSourceProvider class for the java.util.HashMap type and application/x-ms-application mediaType. Ensure that a javax.ws.rs.ext.MessageBodyWriter exists in the JAX-RS application for the type and media type specified.
[10:43:52:885 IST 07/02/12] 0000001e RequestProces I org.apache.wink.server.internal.RequestProcessor logException The following error occurred during the invocation of the handlers chain: WebApplicationException (500 - Internal Server Error) with message 'null' while processing GET request sent to http://localhost:9080/jaxrs_module/echo/upload/getSiteNames
The solution I choose is to wrap a Map and use it for the return param.
#XmlRootElement
public class JaxrsMapWrapper {
private Map<String,String> map;
public JaxrsMapWrapper(){
}
public void setMap(Map<String,String> map) {
this.map = map;
}
public Map<String,String> getMap() {
return map;
}
}
and the method signature will go like this
#GET
public JaxrsMapWrapper getMap()
Your problem is that the default serialization strategy (use JAXB) means that you can't serialize that map directly. There are two main ways to deal with this.
Write an XmlAdaptor
There are a number of questions on this on SO but the nicest explanation I've seen so far is on the CXF users mailing list from a few years ago. The one tricky bit (since you don't want an extra wrapper element) is that once you've got yourself a type adaptor, you've got to install it using a package-level annotation (on the right package, which might take some effort to figure out). Those are relatively exotic.
Write a custom MessageBodyWriter
It might well be easier to write your own code to do the serialization. To do this, you implement javax.ws.rs.ext.MessageBodyWriter and tag it with #Provider (assuming that you are using an engine that uses that to manage registration; not all do for complex reasons that don't matter too much here). This will let you produce exactly the document you want from any arbitrary type at a cost of more complexity when writing (but at least you won't be having complex JAXB problems). There are many ways to actually generate XML, with which ones to choose between depending on the data to be serialized
Note that if you were streaming the data out rather than assembling everything in memory, you'd have to implement this interface.
Using CXF 2.4.2, it supports returning Map from the api. I use jackson-jaxrs 1.9.6 for serialization.
#Path("participation")
#Consumes({"application/json"})
#Produces({"application/json"})
public interface SurveyParticipationApi {
#GET
#Path("appParameters")
Map<String,String> getAppParameters();
....
}
With CXF 2.7.x use
WebClient.postCollection(Object collection, Class<T> memberClass, Class<T> responseClass)
,like this in your rest client code.
(Map<String, Region>) client.postCollection(regionCodes, String.class,Map.class);
for other collections use WebClient.postAndGetCollection().