https://cloud.spring.io/spring-cloud-static/spring-cloud-stream-binder-kafka/3.0.0.M3/reference/html/spring-cloud-stream-binder-kafka.html#_programming_model shows an example where the input topic can be set using the property spring.cloud.stream.bindings.process_in.destination.
Now I want to use dependency injection, e.g.
#Bean
public java.util.function.Consumer<KStream<Object, String>> process(JavaMailSender mailSender) {...}
When starting the application (based on Spring Boot) the property spring.cloud.stream.bindings.process_in.destination is ignored, and instead the input topic input is subscribed.
EDIT: Here is the Kotlin code (without imports)
Mailer.kt:
#Configuration
class Mailer {
#Bean
fun sendMail(/*mailSender: JavaMailSender*/) = Consumer<KStream<Any, Mail>> { input ->
input.foreach { _, mail -> println("mail = $mail") }
}
}
Mail.kt:
data class Mail(var from: String = "", var to: String = "", var subject: String = "", var body: String = "")
Application.kt:
#SpringBootApplication
class Application
fun main(args: Array<String>) {
runApplication<Application>(*args) {
}
}
application.yml::
spring.cloud.stream:
bindings.sendMail_in.destination: mail
kafka.binder.configuration.listeners: PLAINTEXT://localhost:9092
There were a few issues in the binder that didn't properly autowire the beans provided to a function/consumer bean. Latest snapshot solves those problems though. Please make sure that you are using the latest snapshot (3.0.0.BUILD-SNAPSHOT). Here is a sample application that works with the same scenario that you provided.
Related
I was browsing through a lot of articles and blogs to find the proper way to get the following running. I already achived the following:
Spring Boot Application - works
Imperative Spring Data MongoDB Connection to my Mongo Atlas Cluster - works
Spring GraphQL Starter implementation (Resolvers, etc.) - works
Now I want to implement my last requirement. To have GraphQL subscriptions working I need to integrate the Spring Data MongoDB Reactive dependency and create a new GraphQL Resolver for Subscriptions. Here is the code that I added to the already working app (hopefully the code fragments give enough info to help me out).
Gradle.kt
implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive")
MyApp.kt
#SpringBootApplication
#EnableReactiveMongoRepositories(basePackages = ["com.myapp"])
#EnableMongoRepositories(basePackages = ["com.myapp"])
class MyApp
fun main(args: Array<String>) {
runApplication<MyApp>(*args)
}
SubscriptionResolver.kt
#Component
class SubscriptionResolver(
private val characterReactiveRepository: CharacterReactiveRepository
) : GraphQLSubscriptionResolver {
fun allCharacters(): Flux<Character> {
return characterReactiveRepository.findAll()
}
}
CharacterReactiveRepository.kt
interface CharacterReactiveRepository : ReactiveMongoRepository<Character, String>
character.graphqls
type Subscription {
allCharacters: [Character]!
}
Error
SchemaClassScannerError: Unable to match type definition (NonNullType{type=ListType{type=TypeName{name='Character'}}}) with java type (reactor.core.publisher.Flux<com.backend.domain.Character>): Java class is not a List or generic type information was lost: reactor.core.publisher.Flux<com.backend.domain.Character>
Detailed Exception
https://pastebin.com/sEWmDaTE
Edit 1
#Component
class SubscriptionResolver(
private val characterReactiveRepository: CharacterReactiveRepository
) : GraphQLSubscriptionResolver {
fun allCharacters(): Publisher<Character> {
return characterReactiveRepository.findAll()
}
}
According to the sample, you should return:
#Component
class SubscriptionResolver(
private val characterReactiveRepository: CharacterReactiveRepository
) : GraphQLSubscriptionResolver {
fun allCharacters(): Publisher<List<Character>> {
return characterReactiveRepository.findAll()
}
}
Example application you can find here https://github.com/graphql-java-kickstart/samples/tree/master/spring-boot-webflux
My problem was that I defined the subscription's method's return value as [Character]! meaning an array. But since Flux<Character> or Publisher<Character> is not an array but a single type in that context the resolving failed all the time.
Changing the schema to the following helped:
type Subscription {
allCharacters: Character
}
I'm using spring-integration bundled with spring-batch and got stuck trying to write integration tests to test the whole flow, not just single config.
I've created Embedded Sftp Server for this tests and trying to send message to sftpInboundChannel - the message is sent, but nothing happens, but when i send this message to the next channel (after sftpInboundChannel) it goes ok. Also i'm not able to load test source properties, even though i'm using #TestPropertySource annotation.
This are my class annotations
#TestPropertySource(properties = {
//here goes all the properties
})
#EnableConfigurationProperties
#RunWith(SpringRunner.class)
#Import({TestConfig.class, SessionConfig.class})
#ActiveProfiles("it")
#SpringIntegrationTest
#EnableIntegration
#SpringBootTest
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
This is my class body
#Autowired
private PollableChannel sftpInboundChannel;
#Autowired
private SessionFactory<ChannelSftp.LsEntry> defaultSftpSessionFactory;
#Autowired
private EmbeddedSftpServer server;
#Test
public void shouldDoSmth() {
RemoteFileTemplate<ChannelSftp.LsEntry> template;
try {
template = new RemoteFileTemplate<>(defaultSftpSessionFactory);
SftpTestUtils.moveToRemoteFolder(template);
final List<ChannelSftp.LsEntry> movedFiles = SftpTestUtils.listFilesFromDirectory("folder/subfolder", template);
log.info("Moved file {}", movedFiles.size());
final MessageBuilder<String> messageBuilder = MessageBuilder.withPayload("Sample.txt") // path to file
.setHeader("file_Path", "Sample.txt")
boolean wasSent = this.sftpInboundChannel.send(messageBuilder.build());
log.info("Was sent to sftpInboundChannel channel {}", wasSent);
log.info("message {}", messageBuilder.build());
} finally {
SftpTestUtils.cleanUp();
}
}
To the case of not read the property file one solution is add in your Test class something like this:
#BeforeClass
public static void beforeClass() {
System.setProperty("propertyfile", "nameOfFile.properties");
}
A second way is to create a xml (or class) config where you add the tag:
<context:property-placeholder
location="nameOfFile.properties"
ignore-resource-not-found="true" system-properties-mode="OVERRIDE" />
and your file will be localized.
The property file should be inside of resources folder.
I receive the following error message:
Error resolving template [catalog/getCatalogItemFromCatalog/catalogItemId/3916677], template might not exist or might not be accessible by any of the configured Template Resolvers
I am trying to reach my service and the method using this url:
http://192.168.99.100:31003/catalog/getCatalogItemFromCatalog/catalogItemId/3916677
Controller:
#Controller
#RequestMapping("catalog")
public class CatalogController {
#GetMapping("/getCatalogItemFromCatalog/catalogItemId/{catalogItemId}")
public CatalogItem getCatalogItemFromCatalog(#PathVariable Integer catalogItemId){
List<Catalog> catalogs = getAllCatalogs();
Optional<CatalogItem> optionalCatalogItem = Optional.empty();
for(Catalog catalog : catalogs){
optionalCatalogItem = catalog.getCatalogItems().stream().filter(it -> it.getCatalogItemId().equals(catalogItemId)).findFirst();
}
return optionalCatalogItem.orElse(null);
}
#GetMapping("/system/ipaddr")
public String getIpAddr() {
List<String> response;
response = runSystemCommandAndGetResponse(IP_ADDR);
return new Gson().toJson(response);
}
}
When I curl
http://192.168.99.100:31003/catalog/system/ipaddr
I have no issues.
I am testing for hours now and nothing seems to work, I have no idea why its failing tho.
you have #Controller on your class which means spring will try to resolve the return type of all your methods inside the controller using all the available templateResolvers.
by using #ResponseBody spring will wrap the return type inside the response (after converting it) directly then returns it to the client, it's similar to using #RestController instead #Controller
Is there a way to call a method in a Grails service, from a Scala class that is running on the same JVM?
I have seen something similar done from Groovy/Griffon but cannot figure out how to accomplish that in Grails. (http://www.jroller.com/aalmiray/entry/griffon_groovy_scala_working_together)
Basically, one of my Grails controllers calls some Scala code, which should return some values asynchronously. So, I guess, the only way to return those values is by calling back a method in a Grails service.
I found a way of doing it, inspired by the link in the question above, and one of the FAQs in the Grails website.
On the Scala side:
Declare an object similar to the following:
package scalaCallback
object ScalaCallback{
var cback: {def callback(example: String)} = null
def setCallback(cb: {def callback(example: String)}){
cback = cb
}
def invokeCallback(example: String){
if(callback != null) cback.callback(example)
}
}
On the Grails side:
Create a class in src/groovy similar to the following:
package groovyCallback
import org.codehaus.groovy.grails.commons.ApplicationHolder
class GroovyCallback{
private GroovyCallback() {}
private static final INSTANCE = new GroovyCallback()
static getInstance(){ return INSTANCE }
void callback(String example){
ApplicationHolder.application.mainContext.yourService.yourMethod(example)
}
}
In your BootStrap.groovy init add the following:
scalaCallback.cback = groovyCallback.GroovyCallback.getInstance()
When you call invokeCallback("example") in Scala, it will call yourService.yourMethod("example")
Note: the jar file with your Scala class should be in the lib folder of you Grails application
Your Grails service is a Spring bean. #Autowire the service into your Scala class (it will need to be a bean/#Component) and call the method.
EDIT - added example:
For example (using Java, not Scala but the approach is exactly the same):
Java code calling service:
package grailstest;
#Component
public class ServiceInjectionTester {
#Autowired TestService testService;
public String testTheService() {
return testService.serviceMethod();
}
}
Service:
class TestService {
String serviceMethod() {
return "success"
}
}
In Config.groovy:
grails.spring.bean.packages = [ "grailstest" ]
You can also wire your Java/Scala bean into your Grails classes:
class TestController {
#Autowired
ServiceInjectionTester serviceInjectionTester
def index = {
render(text: serviceInjectionTester.testTheService())
}
}
References:
Grails Reference 8.4 - Using Services from Java
Spring: The Foundation for Grails
I am working on a REST based interface where people get a json file. The client needs to access the file from another Domain. I use jsonp which works so far. My problem is the rendering in Grails. At the moment I use the 'as JSON' to marshalling the object:
render "${params.jsoncallback}(${user as JSON})"
The Json file getting to the client inclused all attributes, incluing the id and class, which I do not want to have in there. In case it is not jsonp, I do it this way, which works great:
render(contentType:'text/json'){
userName user.userName
userImage user.userImage
:
:
}
So how do I get the id and class attributes out of the json when rendering "user as JSON"? Any idea?
best regards,
Klaas
You can get rid of the class and id properties in the JSON result by creating a custom ObjectMarshaller.
// CustomDomainMarshaller.groovy in src/groovy:
import grails.converters.JSON;
import org.codehaus.groovy.grails.web.converters.ConverterUtil;
import org.codehaus.groovy.grails.web.converters.exceptions.ConverterException;
import org.codehaus.groovy.grails.web.converters.marshaller.ObjectMarshaller;
import org.codehaus.groovy.grails.web.json.JSONWriter;
import org.springframework.beans.BeanUtils;
public class CustomDomainMarshaller implements ObjectMarshaller<JSON> {
static EXCLUDED = ['metaClass','class','id','version']
public boolean supports(Object object) {
return ConverterUtil.isDomainClass(object.getClass());
}
public void marshalObject(Object o, JSON json) throws ConverterException {
JSONWriter writer = json.getWriter();
try {
writer.object();
def properties = BeanUtils.getPropertyDescriptors(o.getClass());
for (property in properties) {
String name = property.getName();
if(!EXCLUDED.contains(name)) {
def readMethod = property.getReadMethod();
if (readMethod != null) {
def value = readMethod.invoke(o, (Object[]) null);
writer.key(name);
json.convertAnother(value);
}
}
}
writer.endObject();
} catch (Exception e) {
throw new ConverterException("Exception in CustomDomainMarshaller", e);
}
}
}
You'll need to register in you grails-app/conf/BootStrap.groovy:
class BootStrap {
def init = { servletContext ->
grails.converters.JSON.registerObjectMarshaller(new CustomDomainMarshaller())
}
def destroy = {}
}
This should work in Grails >= 1.1
thanks for the 'quick' reply!
Man, it looks so easy in the end and took so long to figure out.
I got it working doing a map out of the values I needed and rendered them 'as json' like this:
def userProfile = user.get(randomUser)
def jsonData = [
username: userProfile.userName,
userimage: userProfile.userImage,
userstreet: userProfile.userStreet,
:
:
] as JSON
println jsonData
voila, there was the json I needed :)
It doesn't seem to me as if the JSON auto marshaller supports this.
You could use FlexJSON which allows you to exlude certain properties and wrap it into a custom Codec.
Also see here.