We have a spring batch application developed 2 years back using the old style of XML configuration built using gradle. Simple setup added the job with retries for a custom runtime exception. We don't have the intention to migrate this application to latest annotation based configs as it is running in production now.
Recently discovered that retry is not at all working for some mysterious reason. I tried to upgrade the Spring batch versions locally to the latest but its not helping. Discovered this initially at one of the step calling a #Service class to fetch some data in the #PostContruct, thought it was because of #PostContruct, created an additional step using a Tasklet and tried. I tried all options, the latest change is just tried to throw an java.lang.Exception directly but does not work. Tried #Retryable at the class and method level, never worked, Now I started to wonder, would this retry ever works? I'm giving up on this but desperately needed some help. Anybody who can provide clue or solution to solve this problem would be my Hero! Thank you!
Some code section(Groovy code, some unnecessary Java syntax never used) here(I have cut short some of the methods to show the actual area of concern, but feel free to ask for more config changes) -
dependencies {
testCompile "org.hamcrest:hamcrest-all:1.3"
testCompile "info.cukes:cucumber-groovy:${cukes.version}"
testCompile "info.cukes:cucumber-junit:${cukes.version}"
testCompile "junit:junit:4.11"
testCompile "org.spockframework:spock-core:${spock.version}"
testCompile "org.spockframework:spock-spring:${spock.version}"
compile "org.codehaus.groovy:groovy-all:${groovy.version}"
compile "org.springframework:spring-test:${spring.version}"
compile "org.springframework:spring-core:${spring.version}"
compile "org.springframework.batch:spring-batch-core:4.0.1.RELEASE"
compile "org.springframework.batch:spring-batch-infrastructure:4.0.1.RELEASE"
compile "org.springframework.retry:spring-retry:1.2.2.RELEASE"
compile "org.springframework.batch:spring-batch-test:4.0.1.RELEASE"
compile "org.springframework.data:spring-data-jpa:1.9.0.RELEASE"
compile "org.springframework.data:spring-data-commons:1.9.0.RELEASE"
compile "org.springframework:spring-web:${spring.version}"
...
}
<batch:job id="facebookPermissionsReminderEmailJob" restartable="true">
<batch:step id="businessPageProcess" parent="faultTolerance" next="facebookPermissionsReminderEmailStep">
<tasklet ref="businessPageProcessor" retry-limit="5">
<retryable-exception-classes>
<include class="java.lang.Exception"/>
<include class="com.reachlocal.data.synchronizer.model.ApiException"/>
</retryable-exception-classes>
<batch:retry-listeners>
<batch:listener ref="retryLoggerListener" />
</batch:retry-listeners>
</tasklet>
</batch:step>
<bean id="faultTolerance" class="org.springframework.batch.core.step.item.FaultTolerantStepFactoryBean" abstract="true">
<property name="backOffPolicy">
<bean class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
<property name="initialInterval" value="180000" />
<property name="multiplier" value="2" />
</bean>
</property>
</bean>
#Slf4j
#Scope("step")
#Component("businessPageProcessor")
class BusinessPageProcessor implements Tasklet {
#Autowired
BusinessPagesService businessPagesService
#Autowired
PermissionReminderDetails reminderDetails
Map<String, BusinessPage> nameToPageMap = [:]
Date currentDate
#Override
RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
log.info("Inside FacebookFirstReminderProcessor.initProcessorDetails() - retrieving facebook Business info")
List<BusinessPage> businessPages = businessPagesService.getAll()
log.info("Business Pages info retrieved from facebook: ${businessPages}")
businessPages.forEach({ page ->
String pageUrl = extractPageName(page.link ? page.link : page.pageResponse?.link)
nameToPageMap.put(pageUrl, page)
})
log.info("Generated Page to Status Map is: ${nameToPageMap}")
currentDate = new Date()
log.info("--------DATE-------- : run date is ${currentDate} for current batch process")
reminderDetails.currentDate = this.currentDate
reminderDetails.nameToPageMap = this.nameToPageMap
RepeatStatus.FINISHED
}
}
#Service
class BusinessPagesService {
#Autowired
PlatformProperty platformProperty
#Autowired
ApiCaller apiCaller
List<BusinessPage> getAll() {
String businessId = platformProperty.facebookReachLocalBusinessId
getAll(businessId)
}
List<BusinessPage> getAll(String businessId) {
List<BusinessPage> businessPages = []
Assert.isTrue(StringUtils.isNotBlank(businessId), EMPTY_BUSINESS_ID_ERROR_MESSAGE)
String approvedPagesEndPoint = enrichApiUrlForGetApprovedPages(businessId)
String pendingPagesEndPoint = enrichApiUrlForGetPendingPages(businessId)
List<BusinessPage> approvedPages = getBusinessPages(businessId, approvedPagesEndPoint)
//FB does not return access info, so manually setting the value for rest of the workflow logic to work as it is.
approvedPages.stream().forEach({page -> page.accessStatus = FacebookPageAccessStatus.CONFIRMED})
List<BusinessPage> pendingPages = getBusinessPages(businessId, pendingPagesEndPoint)
//FB does not return access info, so manually setting the value for rest of the workflow logic to work as it is.
pendingPages.stream().forEach({page -> page.accessStatus = FacebookPageAccessStatus.PENDING})
businessPages.addAll(approvedPages)
businessPages.addAll(pendingPages)
businessPages
}
List<BusinessPage> getBusinessPages(String businessId, String endPointUrl) {
List<BusinessPage> businessPages = []
while (true) {
log.info("Retrieving Business Pages details for Business Id of: ${businessId} using enriched URL of: ${endPointUrl}")
BusinessPagesResponse response = getSubSetOfBusinessPages(endPointUrl)
log.info("Successfully retrieve Business Pages details ${response}")
if(response.businessPages) {
businessPages.addAll(response.businessPages)
endPointUrl = response.paging?.next
} else {
break
}
if (!endPointUrl) {
break
}
}
businessPages
}
BusinessPagesResponse getSubSetOfBusinessPages (String endPointURL) {
BusinessPagesResponse response
try {
response = (BusinessPagesResponse) apiCaller.call(
endPointURL,
new ParameterizedTypeReference<BusinessPagesResponse>() {},
Optional.empty(),
HttpMethod.GET)
throw new Exception("Test Exception")
//log.info("Successfully retrieve Business Pages details ${response}")
} catch (ApiException apiEx){
if (apiEx.message.contains(EXCESS_DATA_REQUESTED_FAILURE_TEXT)) {
log.error("ExcessDataRequestedException occurred - retrying")
String exceptionClass = apiEx.class.name.concat("-")
throw new Exception(exceptionClass.concat(apiEx.message)) // will trigger retry upto defined max.
} else {
throw apiEx
}
}
//response
}
#Component
class RetryLoggerListener extends RetryListenerSupport {
private Logger logger = LoggerFactory.getLogger(this.getClass())
#Override
<T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,Throwable throwable) {
logger.error("Error occurred during operation {}",throwable.getMessage())
}
}
Related
My db properties are kept in application-test.properties (I am running Springboot application in test profile) and the Datasource is referred through #Autowired annotation. It throws NullPointerException when I try to use datasource.getConnection().
I have referred similar questions and mostly all of them include some solutions with bean xml configurations. In my case I am not explicitly using any bean configurations. Every datasource properties are kept in application-test.properties file and I am referring through it using Datasource. I am a newbie to Springboot and any help would be great.
My repository class
#Repository
public class ActualUserDetailsDAO {
#Autowired
DataSource dataSource;
public String getPriorityType(String idNo) throws Exception {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
String cxPriorityType = null;
int count = 0;
try {
con = dataSource.getConnection();
String sql = ConfigurationHandler.getInstance().getConfigValue("sample.query");
......................
} catch (SQLException e) {
................
} catch (Exception e) {
..............
} finally {
.................
}
return cxPriorityType;
}
My application properties
spring.main.banner-mode=off
server.port=8180
# Datasource settings
spring.datasource.initialize=true
spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.name=camst2
spring.datasource.url=jdbc:oracle:thin:#..................
spring.datasource.username=username
spring.datasource.password=password
# Tomcat JDBC settings
spring.datasource.tomcat.initial-size=10
spring.datasource.tomcat.max-active=100
spring.datasource.tomcat.min-idle=10
spring.datasource.tomcat.max-idle=100
#spring.datasource.tomcat.max-wait=6000
spring.datasource.tomcat.max-wait=30000
#spring.datasource.tomcat.test-on-connect=true
#spring.datasource.tomcat.test-on-borrow=true
#spring.datasource.tomcat.test-on-return=true
# Tomcat AccessLog
server.tomcat.accesslog.suffix=.log
server.tomcat.accesslog.prefix=access_log
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.directory=/tomcat/logs
server.tomcat.accesslog.pattern=%h %l %u %t %r %s %b %D
My application class
#SpringBootApplication
public class Application {
#Autowired
DataSource dataSource;
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
I found the solution. The problem was in my controller class. I was creating an instance of the my repository class by myself. I should have used #Autowired instead.
#RestController
public class ActualUserDetails implements ActualUserDetailsInt {
#RequestMapping(value = "/foo", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getActualUserDetails(#PathVariable("idNo") String idNo, #RequestParam("lob") String lob,
#RequestParam("offerSellingType") String offerSellingType) {
//do something
ActualUserDetailsDAO actualUserDetailsDAO = new ActualUserDetailsDAO();
actualUserDetailsDAO.getPriorityType(idNo);
//do something
I changed this into following.
#RestController
public class ActualUserDetails implements ActualUserDetailsInt {
#Autowired
ActualUserDetailsDAO actualUserDetailsDAO;
#RequestMapping(value = "/foo", method = RequestMethod.GET, produces =
MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> getActualUserDetails(#PathVariable("idNo") String idNo,
#RequestParam("lob") String lob,
#RequestParam("offerSellingType") String offerSellingType) {
//do something
actualUserDetailsDAO.getPriorityType(idNo);
//do something
Manually creating object of my repository class did not detected dataSource defined inside it. Autowiring my repository class in my controller class seems to solve this problem.
If your data source is not been detected for any reason, I strongly recommend to have a deeper look on your code.
Following are some of the things to look for when this kind of error happens.
Look for the correct folder structure (application properties file
reside under resources folder)
If you are running Spring in a different profile (say test
profile), make sure relevant configurations are written in
application-test.properties
Check for proper annotation in relevant classes
Make sure your application properties are not overridden by any other
configurations
I'm extremely new to String Boot and backend development (maybe three days or less) and I have the desire to build REST API to consume from different clients.
So I started by a simple demo app that has an endpoint called /register. We post a JSON string with username and password to create a new user if not exist.
I was using JPA with HSQLDB and it worked fine persisting on memory. But recently I wanted to use RxJava since I'm familiar with on Android, so I switched to R2DBC with MySQL.
MySQL server is running fine on port 3306 and the app was tested using PostMan on localhost:8080
The problem occurs when I try to query users table or insert entities and it looks like this:
{
"timestamp": "2020-03-22T11:54:43.466+0000",
"status": 500,
"error": "Internal Server Error",
"message": "execute; bad SQL grammar [UPDATE user_entity SET username = $1, password = $2 WHERE user_entity.id = $3]; nested exception is io.r2dbc.spi.R2dbcBadGrammarException: [42102] [42S02] Table \"USER_ENTITY\" not found; SQL statement:\nUPDATE user_entity SET username = $1, password = $2 WHERE user_entity.id = $3 [42102-200]",
"path": "/register"
}
Here's the full logfile for the exception.
I Have been looking for a solution for hours and I seem like not finding it anywhere, so I hope that I will find it here.
Let's break down the project so it's easier to find the solution:
1. database:
2. application.properties:
logging.level.org.springframework.data.r2dbc=DEBUG
spring.datasource.url=jdbc:mysql://localhost:3306/demodb
spring.datasource.username=root
spring.datasource.password=root
3. DatabaseConfiguration:
#Configuration
#EnableR2dbcRepositories
class DatabaseConfiguration : AbstractR2dbcConfiguration() {
override fun connectionFactory(): ConnectionFactory
= ConnectionFactories.get(
builder().option(DRIVER, "mysql")
.option(HOST, "localhost")
.option(USER, "root")
.option(PASSWORD, "root")
.option(DATABASE, "demodb")
.build()
)
}
4. RegistrationController:
#RequestMapping("/register")
#RestController
class RegistrationController #Autowired constructor(private val userService: UserService) {
#PostMapping
fun login(#RequestBody registrationRequest: RegistrationRequest): Single<ResponseEntity<String>>
= userService.userExists(registrationRequest.username)
.flatMap { exists -> handleUserExistance(exists, registrationRequest) }
private fun handleUserExistance(exists: Boolean, registrationRequest: RegistrationRequest): Single<ResponseEntity<String>>
= if (exists) Single.just(ResponseEntity("Username already exists. Please try an other one", HttpStatus.CONFLICT))
else userService.insert(User(registrationRequest.username, registrationRequest.password)).map { user ->
ResponseEntity("User was successfully created with the id: ${user.id}", HttpStatus.CREATED)
}
}
5. UserService:
#Service
class UserService #Autowired constructor(override val repository: IRxUserRepository) : RxSimpleService<User, UserEntity>(repository) {
override val converter: EntityConverter<User, UserEntity> = UserEntity.Converter
fun userExists(username: String): Single<Boolean>
= repository.existsByUsername(username)
}
6. RxSimpleService:
abstract class RxSimpleService<T, E>(protected open val repository: RxJava2CrudRepository<E, Long>) {
protected abstract val converter: EntityConverter<T, E>
open fun insert(model: T): Single<T>
= repository.save(converter.fromModel(model))
.map(converter::toModel)
open fun get(id: Long): Maybe<T>
= repository.findById(id)
.map(converter::toModel)
open fun getAll(): Single<ArrayList<T>>
= repository.findAll()
.toList()
.map(converter::toModels)
open fun delete(model: T): Completable
= repository.delete(converter.fromModel(model))
}
7. RxUserRepository:
#Repository
interface IRxUserRepository : RxJava2CrudRepository<UserEntity, Long> {
#Query("SELECT CASE WHEN EXISTS ( SELECT * FROM ${UserEntity.TABLE_NAME} WHERE username = :username) THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END")
fun existsByUsername(username: String): Single<Boolean>
}
8. And finally, here's my UserEntity
#Table(TABLE_NAME)
data class UserEntity(
#Id
val id: Long,
val username: String,
val password: String
) {
companion object {
const val TABLE_NAME = "user_entity"
}
object Converter : EntityConverter<User, UserEntity> {
override fun fromModel(model: User): UserEntity
= with(model) { UserEntity(id, username, password) }
override fun toModel(entity: UserEntity): User
= with(entity) { User(id, username, password) }
}
}
User and RegistrationRequest are just simple objects with username and password.
What I have missed?
Please leave a comment if you need more code.
I finally managed to solve this mistake!
The problems were so simple yet so sneaky for a beginner:
I was using JDBC in my URL instead of R2DBC
I was using the H2 runtime implementation so it was
expecting an H2 in-memory database
My ConnectionFactory was not very correct
So what I did was the following:
Updated my build.gradle:
Added:
implementation("io.r2dbc:r2dbc-pool") , implementation("dev.miku:r2dbc-mysql:0.8.1.RELEASE") and
runtimeOnly("mysql:mysql-connector-java")
Removed: runtimeOnly("io.r2dbc:r2dbc-h2")
It now looks like this:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.2.5.RELEASE"
id("io.spring.dependency-management") version "1.0.9.RELEASE"
kotlin("jvm") version "1.3.61"
kotlin("plugin.spring") version "1.3.61"
}
group = "com.tamimattafi.backend"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11
repositories {
mavenCentral()
maven(url = "https://repo.spring.io/milestone")
}
dependencies {
//SPRING BOOT
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot.experimental:spring-boot-starter-data-r2dbc")
//KOTLIN
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
//RX JAVA
implementation("io.reactivex.rxjava2:rxjava:2.2.0")
implementation("io.reactivex:rxjava-reactive-streams:1.2.1")
//MYSQL
implementation("dev.miku:r2dbc-mysql:0.8.1.RELEASE")
implementation("io.r2dbc:r2dbc-pool")
runtimeOnly("mysql:mysql-connector-java")
//TEST
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
testImplementation("org.springframework.security:spring-security-test")
testImplementation("io.projectreactor:reactor-test")
testImplementation("org.springframework.boot.experimental:spring-boot-test-autoconfigure-r2dbc")
}
dependencyManagement {
imports {
mavenBom("org.springframework.boot.experimental:spring-boot-bom-r2dbc:0.1.0.M3")
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "1.8"
}
}
Updated my application.properties to this:
spring.r2dbc.url=r2dbc:pool:mysql://127.0.0.1:3306/demodb
spring.r2dbc.username=root
spring.r2dbc.password=root
Updated my DatabaseConfiguration to this (Note that I removed the #EnableR2dbcRepositories because it should be elsewhere) :
#Configuration
class DatabaseConfiguration : AbstractR2dbcConfiguration() {
override fun connectionFactory(): ConnectionFactory
= MySqlConnectionFactory.from(
MySqlConnectionConfiguration.builder()
.host("127.0.0.1")
.username("root")
.port(3306)
.password("root")
.database("demodb")
.connectTimeout(Duration.ofSeconds(3))
.useServerPrepareStatement()
.build()
)
}
Updated my Application class (I brought the annotation here):
#SpringBootApplication
#EnableR2dbcRepositories
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
IT WORKS NOW! I hope someone will find this helpful, Happy Coding!
In application.properties you need to set the spring.jpa.hibernate.ddl-auto property.
The options are:
validate: validate the schema, makes no changes to the database.
update: update the schema.
create: creates the schema, destroying previous data.
create-drop: drop the schema when the SessionFactory is closed explicitly, typically when the application is stopped.
none: does nothing with the schema, makes no changes to the database
I have a large Spring application that is set up without XML using only annotations. I have made some changes to this application and have a separate project with what should be almost all the same code. However, in this separate project, Togglz seems to be using some sort of default config instead of the TogglzConfig file I've set up.
The first sign that something was wrong was when I couldn't access the Togglz console. I get a 403 Forbidden error despite my config being set to allow anyone to use it (as shown on the Togglz site). I then did some tests and tried to see a list of features and the list is empty when I call FeatureContext.getFeatureManager().getFeatures() despite my Feature class having several features included. This is why I think it's using some sort of default.
TogglzConfiguration.java
public enum Features implements Feature {
FEATURE1,
FEATURE2,
FEATURE3,
FEATURE4,
FEATURE5;
public boolean isActive() {
return FeatureContext.getFeatureManager().isActive(this);
}
}
TogglzConfiguration.java
#Component
public class TogglzConfiguration implements TogglzConfig {
public Class<? extends Feature> getFeatureClass() {
return Features.class;
}
public StateRepository getStateRepository() {
File properties = [internal call to property file];
try {
return new FileBasedStateRepository(properties);
} catch (Exception e) {
throw new TogglzConfigException("Error getting Togglz configuration from " + properties + ".", e);
}
}
#Override
public UserProvider getUserProvider() {
return new UserProvider() {
#Override
public FeatureUser getCurrentUser() {
return new SimpleFeatureUser("admin", true);
}
};
}
}
SpringConfiguration.java
#EnableTransactionManagement
#Configuration
#ComponentScan(basePackages = { "root package for the entire project" }, excludeFilters =
#ComponentScan.Filter(type=FilterType.ANNOTATION, value=Controller.class))
public class SpringConfiguration {
#Bean
public TransformerFactory transformerFactory() {
return TransformerFactory.newInstance();
}
#Bean
public DocumentBuilderFactory documentBuilderfactory() {
return DocumentBuilderFactory.newInstance();
}
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
My project finds a bunch of other beans set up with the #Component annotation. I don't know if the problem is that this component isn't being picked up at all or if Togglz simply isn't using it for some reason. I tried printing the name of the FeatureManager returned by FeatureContext.getFeaturemanager() and it is FallbackTestFeatureManager so this seems to confirm my suspicion that it's just not using my config at all.
Anyone have any ideas on what I can check? I'm flat out of ideas, especially since this is working with an almost completely the same IntelliJ project on my machine right now. I just can't find out what's different about the Togglz setup or the Spring configurations. Thanks in advance for your help.
I finally had my light bulb moment and solved this problem. In case anyone else has a similar issue, it seems my mistake was having the Togglz testing and JUnit dependencies added to my project but not limiting them to the test scope. I overlooked that part of the site.
<!-- Togglz testing support -->
<dependency>
<groupId>org.togglz</groupId>
<artifactId>togglz-testing</artifactId>
<version>2.5.0.Final</version>
<scope>test</scope>
</dependency>
Without that scope, I assume these were overriding the Togglz configuration I created with a default test configuration and that was causing my issue.
I would really like to use YAML config for Spring Boot, as I find it quite readable and useful to have a single file showing what properties are active in my different profiles. Unfortunately, I'm finding that setting properties in application.yml can be rather fragile.
Things like using a tab instead of spaces will cause properties to not exist (without warnings as far as I can see), and all too often I find that my active profiles are not being set, due to some unknown issue with my YAML.
So I was wondering whether there are any hooks that would enable me to get hold of the currently active profiles and properties, so that I could log them.
Similarly, is there a way to cause start-up to fail if the application.yml contains errors? Either that or a means for me to validate the YAML myself, so that I could kill the start-up process.
In addition to other answers: logging active properties on context refreshed event.
Java 8
package mypackage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
#Slf4j
#Component
public class AppContextEventListener {
#EventListener
public void handleContextRefreshed(ContextRefreshedEvent event) {
printActiveProperties((ConfigurableEnvironment) event.getApplicationContext().getEnvironment());
}
private void printActiveProperties(ConfigurableEnvironment env) {
System.out.println("************************* ACTIVE APP PROPERTIES ******************************");
List<MapPropertySource> propertySources = new ArrayList<>();
env.getPropertySources().forEach(it -> {
if (it instanceof MapPropertySource && it.getName().contains("applicationConfig")) {
propertySources.add((MapPropertySource) it);
}
});
propertySources.stream()
.map(propertySource -> propertySource.getSource().keySet())
.flatMap(Collection::stream)
.distinct()
.sorted()
.forEach(key -> {
try {
System.out.println(key + "=" + env.getProperty(key));
} catch (Exception e) {
log.warn("{} -> {}", key, e.getMessage());
}
});
System.out.println("******************************************************************************");
}
}
Kotlin
package mypackage
import mu.KLogging
import org.springframework.context.event.ContextRefreshedEvent
import org.springframework.context.event.EventListener
import org.springframework.core.env.ConfigurableEnvironment
import org.springframework.core.env.MapPropertySource
import org.springframework.stereotype.Component
#Component
class AppContextEventListener {
companion object : KLogging()
#EventListener
fun handleContextRefreshed(event: ContextRefreshedEvent) {
printActiveProperties(event.applicationContext.environment as ConfigurableEnvironment)
}
fun printActiveProperties(env: ConfigurableEnvironment) {
println("************************* ACTIVE APP PROPERTIES ******************************")
env.propertySources
.filter { it.name.contains("applicationConfig") }
.map { it as EnumerablePropertySource<*> }
.map { it -> it.propertyNames.toList() }
.flatMap { it }
.distinctBy { it }
.sortedBy { it }
.forEach { it ->
try {
println("$it=${env.getProperty(it)}")
} catch (e: Exception) {
logger.warn("$it -> ${e.message}")
}
}
println("******************************************************************************")
}
}
Output like:
************************* ACTIVE APP PROPERTIES ******************************
server.port=3000
spring.application.name=my-app
...
2017-12-29 13:13:32.843 WARN 36252 --- [ main] m.AppContextEventListener : spring.boot.admin.client.service-url -> Could not resolve placeholder 'management.address' in value "http://${management.address}:${server.port}"
...
spring.datasource.password=
spring.datasource.url=jdbc:postgresql://localhost/my_db?currentSchema=public
spring.datasource.username=db_user
...
******************************************************************************
I had the same problem, and wish there was a debug flag that would tell the profile processing system to spit out some useful logging. One possible way of doing it would be to register an event listener for your application context, and print out the profiles from the environment. I haven't tried doing it this way myself, so your mileage may vary. I think maybe something like what's outlined here:
How to add a hook to the application context initialization event?
Then you'd do something like this in your listener:
System.out.println("Active profiles: " + Arrays.toString(ctxt.getEnvironment().getActiveProfiles()));
Might be worth a try. Another way you could probably do it would be to declare the Environment to be injected in the code where you need to print the profiles. I.e.:
#Component
public class SomeClass {
#Autowired
private Environment env;
...
private void dumpProfiles() {
// Print whatever needed from env here
}
}
Actuator /env service displays properties, but it doesn't displays which property value is actually active. Very often you may want to override your application properties with
profile-specific application properties
command line arguments
OS environment variables
Thus you will have the same property and different values in several sources.
Snippet bellow prints active application properties values on startup:
#Configuration
public class PropertiesLogger {
private static final Logger log = LoggerFactory.getLogger(PropertiesLogger.class);
#Autowired
private AbstractEnvironment environment;
#PostConstruct
public void printProperties() {
log.info("**** APPLICATION PROPERTIES SOURCES ****");
Set<String> properties = new TreeSet<>();
for (PropertiesPropertySource p : findPropertiesPropertySources()) {
log.info(p.toString());
properties.addAll(Arrays.asList(p.getPropertyNames()));
}
log.info("**** APPLICATION PROPERTIES VALUES ****");
print(properties);
}
private List<PropertiesPropertySource> findPropertiesPropertySources() {
List<PropertiesPropertySource> propertiesPropertySources = new LinkedList<>();
for (PropertySource<?> propertySource : environment.getPropertySources()) {
if (propertySource instanceof PropertiesPropertySource) {
propertiesPropertySources.add((PropertiesPropertySource) propertySource);
}
}
return propertiesPropertySources;
}
private void print(Set<String> properties) {
for (String propertyName : properties) {
log.info("{}={}", propertyName, environment.getProperty(propertyName));
}
}
}
If application.yml contains errors it will cause a failure on startup. I guess it depends what you mean by "error" though. Certainly it will fail if the YAML is not well formed. Also if you are setting #ConfigurationProperties that are marked as ignoreInvalidFields=true for instance, or if you set a value that cannot be converted. That's a pretty wide range of errors.
The active profiles will probably be logged on startup by the Environment implementation (but in any case it's easy for you to grab that and log it in your launcher code - the toString() of teh Environment will list the active profiles I think). Active profiles (and more) are also available in the /env endpoint if you add the Actuator.
In case you want to get the active profiles before initializing the beans/application, the only way I found is registering a custom Banner in your SpringBootServletInitializer/SpringApplication (i.e. ApplicationWebXml in a JHipster application).
e.g.
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder)
{
// set a default to use when no profile is configured.
DefaultProfileUtil.addDefaultProfile(builder.application());
return builder.sources(MyApp.class).banner(this::printBanner);
}
/** Custom 'banner' to obtain early access to the Spring configuration to validate and debug it. */
private void printBanner(Environment env, Class<?> sourceClass, PrintStream out)
{
if (env.getProperty("spring.datasource.url") == null)
{
throw new RuntimeException(
"'spring.datasource.url' is not configured! Check your configuration files and the value of 'spring.profiles.active' in your launcher.");
}
...
}
where i have to read and write userdata.properties from UI, every thing up to now is working like reading and writing ,before i mention the hardcoded direct path in file like this
File f = new File("D:\\user\\userdata.properties")
but my problem is if i mention like this i cannot change the path,for that i have to mention
D:\user\userdata.properties in another path.properties file ,now i have to read path.properties file in
File f = new File(........)
please help me how o do that.this is how presently i am using to read userdata.properties
#RequestMapping("/proxy")
public String ProxySettings(Model model) throws Exception {
File f = new File("D:\\sahi\\userdata.properties");
//String path = MobeeProxyChangeController.class.getResourceAsStream("/property/path.properties").toString();
Properties properties = new Properties();
try {
properties.load(new FileInputStream(f));
String getHost = properties.getProperty("ext.http.proxy.host");
String getPort = properties.getProperty("ext.http.proxy.port");
model.addAttribute("proxyHost", getHost.trim());
model.addAttribute("proxyPort", getPort.trim());
} catch (Exception e) {
e.printStackTrace();
}
return "proxyFile";
}
Thanks in advance
venu
First of all, you shouldn't call the load method for each request. That will turn out very expensive for you.
Secondly, since you're already using spring, there is no need to read a properties file this way. You can just inject it.
I suggest creating fields for proxyHost and proxyPort in your class as fields.
public class MyController {
...
private String proxyHost;
private String proxyPort;
#RequestMapping("/proxy")
public String ProxySettings(Model model) throws Exception {
model.addAttribute("proxyHost", this.proxyHost);
model.addAttribute("proxyPort", this.proxyPort);
return "proxyFile";
}
// provide setters for the above fields or use #Autowired
}
Your spring configuration would look like:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:userdate.properties" />
</bean>
<bean class="com.foo.MyController">
<property name="proxyHost" value="${ext.http.proxy.host}"/>
<property name="proxyPort" value="${ext.http.proxy.port}"/>
</bean>
You can read the userdata.properties either from the classpath or file system depending on where you keep it.
Put your properties file on the classpath and reference it via a MessageSource
Example:
Properties file "foo.properties":
foo=bar
baz=phleem
Java code:
public static void main(final String[] args) {
final ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("foo");
System.out.println(messageSource.getMessage("foo", new Object[0], Locale.getDefault()));
}
Output:
bar
You can let Spring inject that MessageSource, but I think you have to do that through XML (or through #Configuration). But you can also do it manually, however, you should only do it once per class, not once per request.
OK, here's an example of how you could wire it together using Spring:
XML configuration:
<bean id="proxySettings" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="userdata" />
</bean>
Controller class:
#Controller
public class ProxyController {
private static final Locale LOCALE = Locale.getDefault();
private static final Object[] EMPTY_ARGS = new Object[0];
#Autowired #Qualifier("proxySettings")
private MessageSource messageSource;
#RequestMapping("/proxy")
public String proxySettings(final Model model) throws Exception {
model.addAttribute("proxyHost", messageSource.getMessage("ext.http.proxy.host", EMPTY_ARGS, LOCALE));
model.addAttribute("proxyPort", messageSource.getMessage("ext.http.proxy.port", EMPTY_ARGS, LOCALE));
return "proxyFile";
}
}