I'm trying to receive message through Grpc service, send it to Kafka Emitter, and return some value back.
#Singleton
#GrpcService
public class MessageService implements protobuf.MessageService{
#Inject
#Channel("hello-out")
Emitter<Record<String, GeneratedMessageV3>> emitter;
#Override
public Uni<EnvelopeReply> processMessage(Envelope request) {
return Uni.createFrom().completionStage(
emitter.send(Record.of(request.getKey(), request))
).replaceWith(EnvelopeReply.newBuilder().build());
}
}
During build, I'm getting next error:
Error injecting org.eclipse.microprofile.reactive.messaging.Emitter<io.smallrye.reactive.messaging.kafka.Record<java.lang.String, com.google.protobuf.GeneratedMessageV3>> com.test.MessageService.emitter
...
Caused by: javax.enterprise.inject.spi.DefinitionException: SRMSG00019: Unable to connect an emitter with the channel `hello-out`
It works properly with Rest resource.
Without going deeply into the topic, here's my solution:
You can't inject Kafka Emmiter directly to grpc service, it'll throw an exception.
GrpcService <- Emitter<Record...>
Possible reason(I'm sure Quarkus team will reply lower with correct solution :)) is that all GrpcServices are of #Singleton type, and they can't have lazy-initialised properties, they need to have something directly injected. Emitter is generated at a later stage.
By adding a wrapper class you're solving all the headaches, so:
GrpcService <- KafkaService <- Emitter<Record...>
#ApplicationScoped
public class KafkaService {
#Inject
#Channel("hello-out")
Emitter<Record<String, GeneratedMessageV3>> emitter;
// Implement this part properly, added just for example
public Emitter<Record<String, GeneratedMessageV3>> getEmitter() {
return emitter;
}
}
...
#Singleton
#GrpcService
public class MessageService implements protobuf.MessageService {
#Inject
KafkaService kafkaService;
#Override
public Uni<EnvelopeReply> processMessage(Envelope request) {
// use metadata if needed
Map<String, String> metadataMap = request.getMetadataMap();
return Uni.createFrom().completionStage(
kafkaService.getEmitter().send(Record.of(request.getKey(), request))
).replaceWith(EnvelopeReply.newBuilder().build());
}
}
Related
I'm using Quarkus 1.2.0.Final.
I have the following REST client:
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#RegisterRestClient(configKey = "<some key>")
public interface SomeClient {
#POST
#Path("<some path>")
SomeResponse someMethod(SomeRequest request);
}
This bean is used in my other beans as a dependency.
And I have the following test case:
#QuarkusTest
class SomeTest {
#Test
void testGetTransactions() { }
}
class SomeClientImpl implements SomeClient {
#Override
public SomeResponse someMethod(SomeRequest request) {
// <the implementation doesn't matter>
return null;
}
}
The test is failing with the following exception:
Suppressed: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type com.example.client.SomeClient and qualifiers [#RestClient]
...
Why the test is failing? If I remove the class class SomeClientImpl implements SomeClient {...} the test is passing. So implementing an interface leads to test failing, which is weird.
Update 1:
I tried the next code and I'm getting the same exception:
#QuarkusTest
class TransactionServiceImplTest {
#Test
void testGetTransactions() {
new SomeClient() {
#Override
public SomeResponse someMethod(SomeRequest request) {
// <the implementation doesn't matter>
return null;
}
};
}
}
Quarkus REST client is based on MicroProfile REST Client. With MP REST Client you are not supposed to implement the REST Client interface -- it will be automatically generated for you.
For example, if you leave the interface as-is like this:
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#RegisterRestClient(configKey = "<some key>")
public interface SomeClient {
#POST
#Path("<some path>")
SomeResponse someMethod(SomeRequest request);
}
You can use the REST Client like this in your application:
#Inject
#RestClient
SomeClient client;
public void doSomething() {
SomeRequest req = // ...
SomeResponse resp = someMethod(req);
}
When your application code calls someMethod(req) what happens is your application makes an HTTP POST request to whatever URL SomeClient.someMethod is configured for, and then the JSON response of that HTTP POST requests is converted into the SomeResponse object using JSON-B or Jackson.
For more information, I would suggest going through the Quarkus REST client guide
If your goal is mock the external REST service that SomeClient normally talks to, I would suggest using a library like MockServer to mock responses to requests by your application via SomeClient. In your test, configure the URL for SomeClient to point to the MockServer that you start as part of the test.
I am trying to return a Single.just(..) from my endpoint.
I have created it using jersey and rx-jersey.
I keep getting this message on my browser:
No serializer found for class io.reactivex.internal.operators.single.SingleJust and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
Here is my code:-
JerseyCOnfig:
#Component
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(RxJerseyServerFeature.class);
register(RxJerseyClientFeature.class);
register(new JacksonJsonProvider(new ObjectMapper().disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)));
register(UserService.class);
}
}
My End point
#Path("/users")
public class UserService {
#GET
#Path("/setup/rx")
#Produces(MediaType.APPLICATION_JSON)
public Single<User> setupRx() {
return Single.just(new User(29));
}
}
User:-
public class User {
private Integer age;
//getters and settters
It doesn't make sense for the jersey service to return any kind of observable. You would use chains of observables to produce concrete files or pages, but not observables.
In your case, the result of the GET method would be User, not instructions on how to get User.
I tried some stuff with spring-cloud-stream. Everything works and now I tried to write some test cases. Unfortunately they are not working. I reduced everything to the following (Everything is in the same boot app):
The Sender:
#EnableBinding(Sender.Emitter.class)
public class Sender {
public interface Emitter {
String CHANNEL = "emitter";
#Output(CHANNEL)
MessageChannel events();
}
private Emitter emitter;
public Sender(Emitter emitter) {
this.emitter = emitter;
}
public void sendMessage(String massage) {
emitter.events().send(MessageBuilder.withPayload(massage).build());
}
}
The Receiver:
#EnableBinding(Receiver.Subscriber.class)
public class Receiver {
public interface Subscriber {
String CHANNEL = "subscriber";
#Input(CHANNEL)
SubscribableChannel events();
}
private String lastMessage;
public String getLastMessage() {
return lastMessage;
}
#StreamListener(Subscriber.CHANNEL)
public void event(String message) {
this.lastMessage = message;
}
}
My config:
spring:
cloud:
stream:
default-binder: rabbit
bindings:
emitter:
destination: testtock
content-type: application/json
subscriber:
destination: testtock
The Test:
#RunWith(SpringRunner.class)
#SpringBootTest
public class BasicTest {
#Autowired
private Receiver receiver;
#Autowired
private Sender sender;
#Test
public void test() throws InterruptedException {
String massage = UUID.randomUUID().toString();
sender.sendMessage(massage);
//Thread.sleep(1000);
assertEquals(massage, receiver.getLastMessage());
}
}
I want use spring-cloud-stream-test-support for testing to not need a AMQP message broker. Outside of testing I use a rabbitmq, there everything is working.
Maybe the spring-cloud-stream-test-support does not really route messages? Or what is the Problem here?
Maybe the spring-cloud-stream-test-support does not really route messages?
Correct; the test binder is just a harness, it doesn't route between bindings; it's unusual to have a producer and consumer binding for the same destination in the same app.
When you send a message in a test, you have to query the binder to ensure it was sent expected. You use a MessageCollector to do that. See the documentation and you can also look at the tests for some of the out of the box apps.
The spring-cloud-stream-test-support provides an ability to test individual Spring Cloud Stream application and uses TestSupportBinder. Hence, this is not meant for end-to-end integration testing like the one you are using above.
For more information on using spring-cloud-stream-test-support and the TestSupportBinder, you can refer the doc here
i am trying to implement server side events.
I have very simple resource exposed by a RESTful web service with Jersey/Grizzly. I try to broadcast the events with the SseBroadcaster. An event is created, whenever a data item comes in and is added to an internal list. A client should open a connection to the URL /events to receive the events.
#Path("sensordataelements")
public class SensorDataResource {
private SseBroadcaster broadcaster = new SseBroadcaster();
#GET
#Path("events")
#Produces(SseFeature.SERVER_SENT_EVENTS)
public EventOutput getServerSentEvents() {
final EventOutput eventOutput = new EventOutput();
broadcaster.add(eventOutput);
return eventOutput;
}
#POST
#Path("/addraw")
#Produces(MediaType.APPLICATION_JSON)
public Response addRawSensorData(String elementBody) {
... data processing stuff ...
cList.add(
new SensorDataElement.SensorDataElementBuilder().id()
.sensorReading(tmpValue)
.build()
);
OutboundEvent evt = new OutboundEvent.Builder()
.data(Float.class, Float.valueOf(tmpValue))
.build();
broadcaster.broadcast(evt);
return Response.status(201).build();
}
...
I tried to connect with
curl -v http://localhost:8080/sensordataapp/sensordataelements/events
The connection is fine, but i do not get any events. I looked at some examples, but got the impression that this should work. What did i miss?
Thanks!
By default, a new instance of the resource class is created for each request. This means that a new broadcaster is created for each request, which isn't what you want. If you want to make the resource class a Singleton, you can simply annotate the class with #Singleton
#Singleton
#Path("sensordataelements")
public class SensorDataResource {
...
}
Now, only one instance of the resource class will be created for the entire application, and it will be shared for all requests.
The other option, is if you inject the broadcaster, instead of instantiating it yourself, you can inject it as a Singleton. Whether or not the resource class is a singleton or not, it will still get injected the same broadcaster instance. To do that, you can do something like the following in your ResourceConfig subclass
public class AppConfig extends ResourceConfig {
public AppConfig() {
register(new AbstractBinder() {
#Override
public void configure() {
bind(new SseBroadcaster()).to(SseBroadcaster.class);
}
});
}
}
Then in your resource class, just inject it
#Path("sensordataelements")
public class SensorDataResource {
#Inject
private SseBroadcaster broadcaster;
See also:
Dependency injection with Jersey 2.0
[EDIT] The problem is with the
register(new ServiceBinder<>(MyService.class));
Jersey generates a warning and ignores the registration for all but the first one (Existing previous registration found for the type); it only considers the type-erased ServiceBinder class to decide there is a conflict.
It looks like I need to use a more sophisticated version of register to get past that issue.
[/EDIT]
In Jersey 1 I was able to use custom injectable providers to inject my objects into both class fields and method parameters, by extending
LazySingletonInjectableProvider
I can't figure out how to port that pattern to Jersey 2 (with hk2 on Tomcat 7). I have read everything I could find on the topic, including Jersey custom method parameter injection with inbuild injection - but I don't want to use a custom annotation, and I am not trying to inject a request parameter.
[EDIT] I made the wrong assumption regarding what works and what doesn't:
Injection into a class field in a ContainerRequestFilter works fine
Injection into a resource, either as class field or method parameter does not work
[EDIT 2]: The InjectionResolver as described below actually doesn't work at all, I have removed it. Jersey already has a ContextInjectionResolver which presumably should take care of the #Context annotation.
I have created and registered an AbstractBinder, and with that class field injection works fine; however method parameter injection doesn't (the binder never gets invoked and the parameter remains null).
I have tried to bind an InjectionResolver but that didn't help either.
Any suggestion on how to make this work would be greatly appreciated... here is the current code:
The HK2 binder:
public class ServiceBinder<T> extends AbstractBinder
{
private final Factory<T> _factory;
private final Class<? extends T> _clazz;
public OsgiServiceBinder(Class<T> clazz)
{
_factory = new ServiceFactory<>(clazz);
_clazz = clazz;
}
protected void configure()
{
bindFactory(_factory).to(_clazz); //.in(RequestScoped.class);
bind(ServiceInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<Context>>() { })
.in(PerLookup.class);
}
}
The injection resolver:
public class ServiceInjectionResolver<T> implements InjectionResolver<Context>
{
private Class<T> _clazz;
public OsgiServiceInjectionResolver(Class<T> clazz)
{
_clazz = clazz;
}
public Object resolve(Injectee injectee, ServiceHandle<?> root)
{
if (_clazz.getCanonicalName().equals(injectee.getRequiredType().getTypeName())) {
return Framework.getService(_clazz);
}
return null;
}
public boolean isConstructorParameterIndicator()
{
return false;
}
public boolean isMethodParameterIndicator()
{
return true;
}
}
The JAX-RS registration:
public class MyApplication extends Application
{
public MyApplication()
{
registerClasses(<resource classes>);
register(new ServiceBinder<>(MyService.class));
}
}
The resource class:
#Path("/schedules")
public class SchedulesResource
{
#Context UriInfo _uriInfo;
// This injection works fine, _service1 is properly initialized
#Context MyService _service1;
#PUT
#Consumes({MediaType.APPLICATION_JSON})
#Path("{jobGroup}/{jobName}")
public Response putSchedule(#Context MyService service2,
...)
{
// The injection of service2 doesn't work...
}
}
The Factory class:
public class ServiceFactory<T> implements Factory<T>
{
private Class<T> _clazz;
protected ServiceFactory(Class<T> clazz)
{
_clazz = clazz;
}
public T provide()
{
return Framework.getService(_clazz);
}
}
public void dispose(T t)
{
}
}
pok
The problem was actually with Jersey component registrations.
Even though I was registering binder instances, Jersey was checking the class (ServiceBinder) and discarding all but the first registration (WARN: existing registration found for the type).
This seems a bit bogus given I am registering instances, and I wish Jersey would fail with an error rather than log a warning when failing to register a component, but the solution is to simply change the registration pattern slightly:
// Doesn't work
register(new ServiceBinder<>(MyService1.class));
register(new ServiceBinder<>(MyService2.class));
// Works like a charm
register(new ServiceBinder(MyService1.class, MyService2.class));
where obviously the ServiceBinder is adjusted to call bindFactory for each supplied service.