Kotlin + Spring Cloud Function + AWS Lambda - spring-boot

Creating a simple spring boot project using kotlin+Gradle and I get the following error from AWS
ATEST]7eaf7391bd074416bb2acc17a521a803, AWS_LAMBDA_INITIALIZATION_TYPE=on-demand, AWS_XRAY_DAEMON_ADDRESS=169.254.79.2:2000, _AWS_XRAY_DAEMON_PORT=2000, AWS_XRAY_CONTEXT_MISSING=LOG_ERROR, AWS_LAMBDA_FUNCTION_VERSION=$LATEST, AWS_ACCESS_KEY=ASIAU7C4QO4VEP4KLVH2, AWS_LAMBDA_FUNCTION_NAME=swaves-beach-rate}
00:14:04.221 [main] INFO org.springframework.cloud.function.adapter.aws.CustomRuntimeInitializer - AWS Handler: org.springframework.cloud.function.adapter.aws.SpringBootStreamHandler
00:14:04.321 [main] INFO lambdainternal.AWSLambda - Starting AWSLambda using Java 11.0.11 on 169.254.24.133 with PID 8 (/var/runtime/lib/aws-lambda-java-runtime-0.2.0.jar started by sbx_user1051 in /var/task)
00:14:04.321 [main] DEBUG lambdainternal.AWSLambda - Running with Spring Boot, Spring
00:14:04.321 [main] INFO lambdainternal.AWSLambda - No active profile set, falling back to default profiles: default
00:14:04.323 [main] DEBUG org.springframework.boot.SpringApplication - Loading source class com.aio.swaves.SwavesApplication
00:14:04.767 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext#204f30ec
00:14:04.807 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
00:14:07.024 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
00:14:07.028 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
00:14:07.039 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
00:14:07.059 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
00:14:07.083 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'swavesApplication'
00:14:07.099 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'greet'
00:14:07.139 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.boot.autoconfigure.AutoConfigurationPackages'
00:14:07.140 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.cloud.function.adapter.aws.CustomRuntimeEventLoop'
00:14:07.205 [main] INFO lambdainternal.AWSLambda - Started AWSLambda in 3.652 seconds (JVM running for 4.394)
00:14:08.210 [main] DEBUG reactor.util.Loggers - Using Slf4j logging framework
No function defined: java.lang.IllegalStateException
java.lang.IllegalStateException: No function defined
at org.springframework.cloud.function.context.AbstractSpringFunctionAdapterInitializer.apply(AbstractSpringFunctionAdapterInitializer.java:189)
at org.springframework.cloud.function.adapter.aws.SpringBootStreamHandler.handleRequest(SpringBootStreamHandler.java:55)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
END RequestId: 7ea16b3f-47d5-4763-b9fe-f97d940cf55c
REPORT RequestId: 7ea16b3f-47d5-4763-b9fe-f97d940cf55c Duration: 5006.33 ms Billed Duration: 5007 ms Memory Size: 1024 MB Max Memory Used: 166 MB Init Duration: 546.69 ms
build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import com.github.jengelman.gradle.plugins.shadow.transformers.*
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
id("org.springframework.boot") version "2.5.4"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
id("com.github.johnrengelman.shadow") version "7.0.0"
kotlin("jvm") version "1.5.21"
kotlin("plugin.spring") version "1.5.21"
}
group = "com.aio"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11
repositories {
mavenCentral()
}
val springCloudVersion = "2020.0.3"
val awsLambdaEventsVersion = "3.10.0"
val awsLambdaCoreVersion = "1.2.1"
dependencies {
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.cloud:spring-cloud-function-web")
implementation("org.springframework.cloud:spring-cloud-function-adapter-aws")
implementation("org.jsoup:jsoup:1.14.2")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
runtimeOnly("com.amazonaws:aws-lambda-java-core:${awsLambdaCoreVersion}")
runtimeOnly("com.amazonaws:aws-lambda-java-events:${awsLambdaEventsVersion}")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(module = "junit")
exclude(module = "mockito-core")
}
testImplementation("io.projectreactor:reactor-test")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("com.ninja-squad:springmockk:3.0.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}
dependencyManagement {
imports {
mavenBom("org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}")
}
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
tasks.assemble{
dependsOn("shadowJar")
}
tasks.withType<Jar> {
manifest {
attributes["Start-Class"] = "com.aio.swaves.SwavesApplication"
}
}
tasks.withType<ShadowJar> {
archiveClassifier.set("aws")
archiveFileName.set("swaves.jar")
dependencies {
exclude(
dependency("org.springframework.cloud:spring-cloud-function-web:3.1.3"))
}
// Required for Spring
mergeServiceFiles()
append("META-INF/spring.handlers")
append ("META-INF/spring.schemas")
append("META-INF/spring.tooling")
transform(PropertiesFileTransformer::class.java) {
val paths = arrayOf("META-INF/spring.factories")
val mergeStrategy = "append"
}
}
application.yml
spring:
cloud:
function:
definition: greet
profiles:
active: production
SwavesApplication.kt
package com.aio.swaves
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
#SpringBootApplication
class SwavesApplication {
#Bean
fun greet(): (String) -> String {
return { "Hello Kotlin" }
}
}
fun main(args: Array<String>) {
runApplication<SwavesApplication>(*args)
}
Notes
Notice that the error says that there is not active profile, so it will select the default, despite that the application.yml is setting an active profile to production.
Also notice that the Bean greet is created, which means that the start-class attribute is correctly configured. I am a bit lost in what is missed, I have make it work using java but not Kotlin any suggestion?

Thanks to Oleg Zhurakousky's answer, I took my time to create samples project and document it at github. In the samples, you will find even how I push the code to AWS Lambda.
The error is that I was using the deprecated SpringBootStreamHandler instead the new FunctionInvoker.

Related

How to use #ImportResource annotation import differrent enviroment config file

I'm trying to use #ImportResource annotation import on different environment config files like reported below:
#EnableFeignClients
#EnableAsync
#ImportResource(locations= {"classpath:${dev|sit}/service-applicationContext.xml","classpath:${dev|sit}/client-applicationContext.xml"})
public class DmrEsbService {
}
Or I'm trying to use other ways to import properties like spring.profiles.active=test, as I reported below:
#Value("${spring.profiles.active}")
public String env;
#Autowired
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
synchronized (lock) {
if (null == ESBClientFactory.applicationContext) {
String[] profiles = {"/profiles/" + env + "/client-applicationContext.xml", "/profiles/" + env + "/service-applicationContext.xml"};
ESBClientFactory.applicationContext = new ClassPathXmlApplicationContext(profiles, applicationContext);
}
}
}
but by using this last way to startup the app, it happened like below:
2021-12-10 15:04:16.005 - [<init> 122] - [INFO] - To enable URLs as dynamic configuration sources, define System property archaius.configurationSource.additionalUrls or make config.properties available on classpath.
2021-12-10 15:04:16.027 - [getInstance 281] - [INFO] - DynamicPropertyFactory is initialized with configuration sources: com.netflix.config.ConcurrentCompositeConfiguration#1fdc92b
2021-12-10 15:04:17.795 - [refresh 554] - [WARN] - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'publicQueryService': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.xxxx.oracle.xmlns.apps.cux.soaprovider.plsql.cux_esb_mass_query_pkg.CUXESBMASSQUERYPKGPortType' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#javax.annotation.Resource(shareable=true, lookup=, name=, description=, authenticationType=CONTAINER, type=class java.lang.Object, mappedName=)}
2021-12-10 15:04:17.810 - [log 173] - [INFO] - Stopping service [Tomcat]
2021-12-10 15:04:17.917 - [logMessage 142] - [INFO] -
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-12-10 15:04:17.917 - [deregister 65] - [INFO] - Unregistering application xxxx with eureka with status DOWN
2021-12-10 15:04:17.918 - [shutdown 888] - [INFO] - Shutting down DiscoveryClient ...
2021-12-10 15:04:17.925 - [shutdown 911] - [INFO] - Completed shut down of DiscoveryClient
2021-12-10 15:04:18.225 - [report 42] - [ERROR] -
***************************
APPLICATION FAILED TO START
***************************
Description:
A component required a bean of type 'com.xxx.oracle.xmlns.apps.cux.soaprovider.plsql.cux_esb_mass_query_pkg.xxx' that could not be found.
Action:
Consider defining a bean of type 'com.xxx.oracle.xmlns.apps.cux.soaprovider.plsql.cux_esb_mass_query_pkg.xxx' in your configuration.
I could not use the command below to package an app:
mvn clean -Dmaven.test.skip=true package -pl xxx -am -Psit
and then I'm using the following:
mvn clean install
could anyone who can tell to how to import different environment in my project?

what actually excludeFilters does in #ComponentScan

I have created a simple spring application to test excludeFilters in #ComponentScan annotation.
TestConfig.java
package com.example.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
#Configuration
#ComponentScan(
excludeFilters = #ComponentScan.Filter(
type = FilterType.REGEX,
pattern = {"com.example.Demo"}
)
)
public class TestConfig {
}
Demo.java
package com.example;
import org.springframework.stereotype.Component;
#Component
public class Demo {
}
Main class ExcludeFilterTest.java
public class ExcludeFilterTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.example");
}
}
when running the application I can see the Demo class is getting identified as a candidate component class and also instance is getting created.
here are the logs
12:05:34.151 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [/Users/sk/learning-development/interview/logging_test/target/classes/com/example/Demo.class]
12:05:34.160 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [/Users/sk/learning-development/interview/logging_test/target/classes/com/example/config/TestConfig.class]
12:05:34.172 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext#687080dc
12:05:34.188 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
12:05:34.275 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
12:05:34.277 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
12:05:34.278 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
12:05:34.282 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'demo'
12:05:34.298 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'testConfig'
Process finished with exit code 0
why the Demo class instance is getting created?
Try this instead.
public class ExcludeFilterTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(com.example.config.TestConfig.class);
}
}

Spring data r2dbc: Problem connecting with mysql - 'r2dbcEntityTemplate' could not be found

My spring boot application is trying to connect to a mysql 5.7 with spring data r2dbc. The setup appears to be pretty straight-forward. However, I kept encountering unexpected exception.
Any help would be appreciated.
Logs:
2021-09-28 10:13:48.591 INFO 12261 --- [ restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data R2DBC repositories in DEFAULT mode.
2021-09-28 10:13:48.651 INFO 12261 --- [ restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 52 ms. Found 1 R2DBC repository interfaces.
//...
2021-09-28 10:13:49.250 WARN 12261 --- [ restartedMain] onfigReactiveWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt:
//...
Error creating bean with name 'healthCheckRepository' defined in application.model.respository.r2dbc.HealthCheckRepository defined in #EnableR2dbcRepositories declared on MainApplication: Cannot resolve reference to bean 'r2dbcEntityTemplate' while setting bean property 'entityOperations'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'r2dbcEntityTemplate' available
//...
Description:
Parameter 0 of constructor in application.service.HealthCheckService required a bean named 'r2dbcEntityTemplate' that could not be found.
Action:
Consider defining a bean named 'r2dbcEntityTemplate' in your configuration.
application.yaml
spring:
r2dbc:
url: r2dbc:pool:mysql//server/database?useUnicode=true&characterEncoding=UTF-8&useSSL=false&useLocalSessionState=true
username: username
password: password
pool:
enabled: true
max-size: 20
validation-query: SELECT 1
initial-size: 5
max-idle-time: 30m
data:
r2dbc:
repositories:
enabled: true
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '2.5.5'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id "io.freefair.lombok" version "6.2.0" //using
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-log4j2'
// implementation 'org.springframework.boot:spring-boot-starter-data-redis-reactive'
implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
runtimeOnly 'dev.miku:r2dbc-mysql:0.8.2.RELEASE'
runtimeOnly 'mysql:mysql-connector-java'
}
It turns out to be a Spring boot issue. Please refer to the below git issue for the resolution
https://github.com/spring-projects/spring-data-r2dbc/issues/659

Karate: How to configure karate mock-servlet using a YAML file instead of application.properties? [duplicate]

This question already has an answer here:
Karate Spring Integration
(1 answer)
Closed 1 year ago.
We have a Spring Boot project which uses an application.yml file to configure the spring context for things like datasource, rabbitmq etc.
The YAML file can be loaded in the karate-config.js to pick-up the baseUrl and that works fine.
function fn() {
var env = karate.env; // get system property 'karate.env'
karate.log('karate.env system property was:', env);
if (!env) {
env = 'dev';
}
karate.log('karate environment set to:', env);
var config = karate.read('classpath:application.yml');
karate.log('baseUrl configured for API tests: '+config.baseUrl)
if (env == 'dev') {
var Factory = Java.type('MockSpringMvcServlet');
karate.configure('httpClientInstance', Factory.getMock());
//var result = karate.callSingle('classpath:demo/headers/common-noheaders.feature', config);
} else if (env == 'stg') {
// customize
} else if (env == 'prod') {
// customize
}
return config;
}
The Mock Spring MVC servlet class taken from the karate mock servlet demo github project:
/**
* #author pthomas3
*/
public class MockSpringMvcServlet extends MockHttpClient {
private final Servlet servlet;
private final ServletContext servletContext;
public MockSpringMvcServlet(Servlet servlet, ServletContext servletContext) {
this.servlet = servlet;
this.servletContext = servletContext;
}
#Override
protected Servlet getServlet(HttpRequestBuilder request) {
return servlet;
}
#Override
protected ServletContext getServletContext() {
return servletContext;
}
private static final ServletContext SERVLET_CONTEXT = new MockServletContext();
private static final Servlet SERVLET;
static {
SERVLET = initServlet();
}
private static Servlet initServlet() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(MockConfig.class);
context.setServletContext(SERVLET_CONTEXT);
DispatcherServlet servlet = new DispatcherServlet(context);
ServletConfig servletConfig = new MockServletConfig();
try {
servlet.init(servletConfig);
} catch (Exception e) {
throw new RuntimeException(e);
}
return servlet;
}
public static MockSpringMvcServlet getMock() {
return new MockSpringMvcServlet(SERVLET, SERVLET_CONTEXT);
}
}
However, the MockConfig.class fails on the EnableAutoConfiguration annotation:
#Configuration
#EnableAutoConfiguration
// #PropertySource("classpath:application.yml") // only for key/value pair .properties files and not YAML
public class MockConfig {
// Global Exception Handler ...
// Services ...
// Controllers ...
#Bean
public NumbersAPI numbersAPI() {
return new NumbersAPI();
}
}
The exception is:
2019-04-23 15:13:21.297 INFO --- [ main] com.intuit.karate : karate.env system property was: null
2019-04-23 15:13:21.306 INFO --- [ main] com.intuit.karate : karate environment set to: dev
2019-04-23 15:13:21.429 INFO --- [ main] com.intuit.karate : baseUrl configured for API tests: http://localhost:50000
2019-04-23 15:13:21.469 INFO --- [ main] o.s.mock.web.MockServletContext : Initializing Spring FrameworkServlet ''
2019-04-23 15:13:21.469 INFO --- [ main] o.s.web.servlet.DispatcherServlet : FrameworkServlet '': initialization started
2019-04-23 15:13:21.496 INFO --- [ main] .s.AnnotationConfigWebApplicationContext : Refreshing WebApplicationContext for namespace '-servlet': startup date [Tue Apr 23 15:13:21 EDT 2019]; root of context hierarchy
2019-04-23 15:13:21.535 INFO --- [ main] .s.AnnotationConfigWebApplicationContext : Registering annotated classes: [class MockConfig]
2019-04-23 15:13:22.215 INFO --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean definition for bean 'dataSource' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Dbcp2; factoryMethodName=dataSource; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Dbcp2.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration$Tomcat; factoryMethodName=dataSource; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Tomcat.class]]
2019-04-23 15:13:22.330 WARN --- [ main] o.s.b.a.AutoConfigurationPackages : #EnableAutoConfiguration was declared on a class in the default package. Automatic #Repository and #Entity scanning is not enabled.
2019-04-23 15:13:22.714 INFO --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.amqp.rabbit.annotation.RabbitBootstrapConfiguration' of type [org.springframework.amqp.rabbit.annotation.RabbitBootstrapConfiguration$$EnhancerBySpringCGLIB$$cafe2407] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2019-04-23 15:13:23.212 WARN --- [ main] .s.AnnotationConfigWebApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Tomcat.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.tomcat.jdbc.pool.DataSource]: Factory method 'dataSource' threw exception; nested exception is org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Cannot determine embedded database driver class for database type NONE. If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).
2019-04-23 15:13:23.215 ERROR --- [ main] o.s.web.servlet.DispatcherServlet : Context initialization failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Tomcat.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.tomcat.jdbc.pool.DataSource]: Factory method 'dataSource' threw exception; nested exception is org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Cannot determine embedded database driver class for database type NONE. If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).
I've used the documentation from the karate mock-servlet github project as guidance to make other karate tests work successfully, but I don't know how to configure the spring context for our karate tests using an application YAML file.
The #PropertySource only supports properties files as you already found out.
Have a look at the following SOF question:
Spring #PropertySource using YAML
Hope this helps.
I was able to get the application.yml file to be respected and loaded by Spring by making the following changes:
Add a YamlPropertySourceFactory util class to the test package:
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
public class YamlPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
Properties propertiesFromYaml = loadYamlIntoProperties(resource);
String sourceName = name != null ? name : resource.getResource().getFilename();
return new PropertiesPropertySource(sourceName, propertiesFromYaml);
}
private Properties loadYamlIntoProperties(EncodedResource resource) throws FileNotFoundException {
try {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return factory.getObject();
} catch (IllegalStateException e) {
// for ignoreResourceNotFound
Throwable cause = e.getCause();
if (cause instanceof FileNotFoundException)
throw (FileNotFoundException) e.getCause();
throw e;
}
}
}
and
Referencing the above in the #PropertySource annotation in the MockConfig class as follows:
#Configuration
#EnableAutoConfiguration
#PropertySource(factory = YamlPropertySourceFactory.class, value = "classpath:application.yml")
public class MockConfig {
// Global Exception Handler ...
#Bean
public ExampleAPIExceptionHandler exampleAPIExceptionHandler() {
return new ExampleAPIExceptionHandler();
}
// Services ...
// Controllers ...
#Bean
public NumbersAPI numbersAPI() {
return new NumbersAPI();
}
}
The Spring context loads and all karate tests pass.
If there is a simpler way to load a YAML file as a Spring context using Karate, then please post it. For now, this will work.
Reference: Use #PropertySource with YAML files

Spring Boot: How to externalize JDBC datasource configuration?

I have following Spring Boot controller code that works. (Some sensitive text was replaced)
package com.sample.server;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
#RestController
public class DetailReportController
{
#RequestMapping(value="/report/detail", method=RequestMethod.GET)
public List<UFGroup> detailReport()
{
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("net.sourceforge.jtds.jdbc.Driver");
dataSource.setUrl("jdbc:jtds:sqlserver://111.11.11.11/DataBaseName;user=sa;password=password");
JdbcTemplate jt = new JdbcTemplate(dataSource);
List<UFGroup> results = jt.query(
"select NID, SCode, SName from UFGroup",
new RowMapper<UFGroup>()
{
#Override
public UFGroup mapRow(ResultSet rs, int rowNum) throws SQLException
{
return new UFGroup(rs.getInt("NID"), rs.getString("SCode"),
rs.getString("SName"));
}
});
return results;
}
private static class UFGroup
{
public int nid;
public String scode;
public String sname;
public UFGroup(int nid, String scode, String sname)
{
this.nid = nid;
this.scode = scode;
this.sname = sname;
}
}
}
Now I want to externalize the configuration of the datasource.
That is, BasicDataSource class, driver class name, datasource URL should be placed into application.properties.
How can I do that?
BTW, I am a newbie of Spring, Spring Boot and even Java Beans. All I have is some Java programming experience, mainly for mobile devices. I've spent a few days to study Spring Boot environment, but I was literally overwhelmed.
So please, give me the exact instruction with concrete example.
UPDATE:
when I applied the answer of M. Deinum, following error occured when I ran the application:
2013-11-18 19:37:54.789 INFO 6868 --- [ main] com.logicplant.uflow.server.Application : Starting Application on zeo-PC with PID 6868 (C:\Projects\uFlow\Dev\Server\Spring\uFlowServer\build\libs\uFlowServer-1.0.0.jar started by zeo)
2013-11-18 19:37:54.830 INFO 6868 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext#7f83698f: startup date [Mon Nov 18 19:37:54 KST 2013]; root of context hierarchy
2013-11-18 19:37:55.931 INFO 6868 --- [ main] o.apache.catalina.core.StandardService : Starting service Tomcat
2013-11-18 19:37:55.932 INFO 6868 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/7.0.42
2013-11-18 19:37:56.009 INFO 6868 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2013-11-18 19:37:56.010 INFO 6868 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1183 ms
2013-11-18 19:37:56.165 INFO 6868 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2013-11-18 19:37:56.165 INFO 6868 --- [ost-startStop-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2013-11-18 19:37:56.242 INFO 6868 --- [ost-startStop-1] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2013-11-18 19:37:56.388 INFO 6868 --- [ost-startStop-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/report/detail],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.util.List<com.logicplant.uflow.server.DetailReportController$UFGroup> com.logicplant.uflow.server.DetailReportController.detailReport()
2013-11-18 19:37:56.438 INFO 6868 --- [ost-startStop-1] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2013-11-18 19:37:56.439 INFO 6868 --- [ost-startStop-1] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2013-11-18 19:37:56.788 INFO 6868 --- [ost-startStop-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 622 ms
2013-11-18 19:37:56.881 INFO 6868 --- [ main] o.apache.catalina.core.StandardService : Stopping service Tomcat
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:53)
at java.lang.Thread.run(Unknown Source)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'detailReportController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private org.springframework.jdbc.core.JdbcTemplate com.logicplant.uflow.server.DetailReportController.jt; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.jdbc.core.JdbcTemplate] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:292)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1139)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:299)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:295)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:665)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:509)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:278)
at com.logicplant.uflow.server.Application.main(Application.java:17)
... 6 more
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private org.springframework.jdbc.core.JdbcTemplate com.logicplant.uflow.server.DetailReportController.jt; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.jdbc.core.JdbcTemplate] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:505)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:289)
... 20 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.jdbc.core.JdbcTemplate] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1051)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:919)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:820)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:477)
... 22 more
For reference, the content of build.grade file (I use Gradle) is as follows: (following M. Deinum's suggestion, I removed the dependency for org.apache.commons.dbcp.)
buildscript {
repositories {
maven { url "http://repo.spring.io/libs-snapshot" }
mavenLocal()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:0.5.0.M5")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'
jar {
baseName = 'uFlowServer'
version = '1.0.0'
}
repositories {
mavenCentral()
maven { url "http://repo.spring.io/libs-snapshot" }
}
dependencies {
compile("org.springframework.boot:spring-boot-starter-web:0.5.0.M5")
compile("com.fasterxml.jackson.core:jackson-databind")
compile("org.springframework:spring-jdbc:4.0.0.M3")
runtime("net.sourceforge.jtds:jtds:1.3.1")
testCompile("junit:junit:4.11")
}
task wrapper(type: Wrapper) {
gradleVersion = '1.8'
}
And this is Application.java file, which is the main source file.
package com.sample.server;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class Application
{
public static void main(String args[])
{
SpringApplication app = new SpringApplication(Application.class);
app.setShowBanner(false);
app.run(args);
}
}
What can be done for the error?
UPDATE:
As M. Deinum suggested, when I changed build.gradle file as follows, the application worked!
buildscript {
repositories {
maven { url "http://repo.spring.io/libs-snapshot" }
mavenLocal()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:0.5.0.M6")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'
jar {
baseName = 'uFlowServer'
version = '1.0.0'
}
repositories {
mavenCentral()
maven { url "http://repo.spring.io/libs-snapshot" }
}
dependencies {
compile("org.springframework.boot:spring-boot-starter-web:0.5.0.M6")
compile("org.springframework.boot:spring-boot-starter-jdbc:0.5.0.M6")
compile("com.fasterxml.jackson.core:jackson-databind")
runtime("net.sourceforge.jtds:jtds:1.3.1")
testCompile("junit:junit:4.11")
}
task wrapper(type: Wrapper) {
gradleVersion = '1.8'
}
Change your controller to the following
#RestController
public class DetailReportController {
#Autowired
private JdbcTemplate jt;
#RequestMapping(value="/report/detail", method=RequestMethod.GET)
public List<UFGroup> detailReport() {
List<UFGroup> results = jt.query(
"select NID, SCode, SName from UFGroup",
new RowMapper<UFGroup>(){
#Override
public UFGroup mapRow(ResultSet rs, int rowNum) throws SQLException {
return new UFGroup(rs.getInt("NID"), rs.getString("SCode"), rs.getString("SName"));
}
});
return results;
}
private static class UFGroup
{
public int nid;
public String scode;
public String sname;
public UFGroup(int nid, String scode, String sname)
{
this.nid = nid;
this.scode = scode;
this.sname = sname;
}
}
}
In src/main/resources add an application.properties with the following
spring.datasource.driverClassName=net.sourceforge.jtds.jdbc.Driver
spring.datasource.url=jdbc:jtds:sqlserver://111.11.11.11/DataBaseName
spring.datasource.username=sa
spring.datasource.password=password
And simply start your application. No xml needed. Spring boot will create the DataSource and will add a default JdbcTemplate instance.
Tip: Remove the dependency for org.apache.commons.dbcp spring-boot will give you the newer (and IMHO better) tomcat connection pool (which despite the name can be used entirely on its own).
I will remodify your code in better approach.
Firstly no need of using new operator in your code as you are using Spring so you can use the powerful feature of Spring i.e Dependency Injection.
#RestController
public class DetailReportController
{ #Required
private JdbcTemplate jt;
//setter for the same
#RequestMapping(value="/report/detail", method=RequestMethod.GET)
public List<UFGroup> detailReport()
{
List<UFGroup> results = jt.query(
"select NID, SCode, SName from UFGroup",
new RowMapper<UFGroup>()
{
#Override
public UFGroup mapRow(ResultSet rs, int rowNum) throws SQLException
{
return new UFGroup(rs.getInt("NID"), rs.getString("SCode"),
rs.getString("SName"));
}
});
return results;
}
private static class UFGroup
{
public int nid;
public String scode;
public String sname;
public UFGroup(int nid, String scode, String sname)
{
this.nid = nid;
this.scode = scode;
this.sname = sname;
}
}
}
application-context.xml
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="100"/>
<property name="maxIdle" value="30"/>
<property name="maxWait" value="16000"/>
<property name="minIdle" value="0"/>
</bean>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<value>database.properties</value>
</property>
</bean>
<bean id="jt" class="org.springframework.jdbc.core.JdbcTemplate;">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="detailReportController" class="your class">
<property name="jt" ref="jt"/>
</bean>
Another suggestion that you can move your code related to database in DAO classes,its very bad practice to have the same in Controller.Else if you want to stick to your approach use Properties class of java.util package
See here

Resources