How to Access Spring Application Configuration Values based on Requests? - spring

In one of my Spring Boot application, I have a controller that needs to read from application.yml for accessing an external API. I have organization setup in the external API, similar to github organization, and each organization comes with its own client ID and secret key.
My application.yml looks something like this.
organization:
abc:
client:
clientId: f29e347add73
clientSecret: dad2404e63ec4cd
xyz:
client:
clientId: 0884340cf3e793
clientSecret: a26ff0119d907e9
Currently, I can pick up a property value in my controller like this.
#Value("${organization.abc.client.clientId}")
private String abcClientId;
#Value("${organization.abc.client.clientSecret}")
private String abcClientSecret;
But what I need to do is, instead of hardcoding, if a request for abc comes, the configuration for abc is picked up and when for xyz comes, the configuration for xyz is picked up. Same for any number of the organization I keep adding to the application.yml file.
Please help me on how to achieve this.

If you can rewrite your applicaiotn.yml as follows, you can read it into a list of object with#ConfigurationProperties.
organization:
list:
-
name: abc
client:
clientId: f29e347add73
clientSecret: dad2404e63ec4cd
-
name: xyz
client:
clientId: 0884340cf3e793
clientSecret: a26ff0119d907e9
Create a class to map properties to list of object:
#Service
#ConfigurationProperties(prefix="organization")
public class ConfigurationService {
private List<Org> list = new ArrayList<>();
//getters and setters
public static class Org {
private String name;
private Client client;
//getters and setters
}
public static class Client {
private String clientId;
private String clientSecret;
//getter and setter
}
}
Now you can access this list like...
#Autowired
ConfigurationService configurationService;

You can inject an Environment (initialized by default by Spring Boot) like this:
#Autowired
private Environment env;
And then use it like:
env.getProperty("my-property")

Related

How to skip bind properties per profile in spring boot(#ConfigurationProperties, #ConstructorBinding)

I want to optionally bind a field to specific class for each profile
the example code is as follows ...
spring:
config:
activate:
on-profile: test1
app:
cash:
conn:
connection-timeout: 1000
response-timeout: 2000
...
---
spring:
config:
activate:
on-profile: test2
#Getter
#Validated
#ConstructorBinding
#ConfigurationProperties(value = "app.cash.conn")
#RequiredArgsConstructor
public class CashBoxConnectionProperties {
#NotNull
#Positive
private final Integer connectionTimeout;
#NotNull
#Positive
private final Integer responseTimeout;
#NotNull
#PositiveOrZero
private final Integer retryMaxAttempts;
#NotNull
#Positive
private final Integer retryMaxDelay;
}
When running as test1 profile, the application runs normally because the properties value is set, but when running as test2 profile, the error 'Binding to target...' occurs because there is no app.cash.conn properties.
The CashBoxConnectionProperties is not required in test2 profile, so is there any other way than to remove #NotNull annotation?
You can annotate the CashBoxConnectionProperties class with #Profile("test2"), which makes it only available when the test2 profile is active. To make it work, the class should be marked as #Component, since #Profile is only applicable to Spring beans.
For more details check the documentation.
You can use validation groups, to turn off validation for specific fields.
I never used this myself, but you can find an explanation here: https://www.baeldung.com/javax-validation-groups

#DefaultValue not working with #ConstructorBinding for Spring boot 2.4.2

Here is an example of my problem. when no value is supplied to default-name in yml file. #DefaultValue should step in and fill with "Name". However, is not how it behaves. An empty string is assigned to defaultName
application.yml:
account:
default-name:
class:
#ConstructorBinding
#ConfigurationProperties(prefix = "account")
public class Account {
private final String defaultName;
public Account(#DefaultValue("Name") String defaultName) {
this.defaultName = defaultName;
}
..
..
}

How to map property in application.yml to JsonNode? (Spring)

In a #ConfigurationProperties bean, I could map the customers property of the application.yml file below to List<Customer>, but I would like to instead map it to a Jackson JsonNode. Is this possible? If so, how?
shop:
name: "Sam's Bikes"
customers:
- name: Lucy
age: 26
- name: James
age: 24
This is what I'd like to achieve:
#ConfigurationProperties("shop")
public class ShopProperties() {
private String name;
private JsonNode customers;
}
Since customers in application.yml are in Array of objects format, i would recomend either to collect them into List<Customer> or List<Map<String,Object>> using below code, #Data is lombok annotation, if you are not using lombok add getters and setters
#ConfigurationProperties("shop")
#Data
#Configuration
public class TestConfig {
private String name;
private List<Map<String,Object>> customers; //[{name=Lucy, age=26}, {name=James, age=24}]
}

Why I receive 404 error use Spring MVC framework?

When I send request http://localhost:8080/pets My server response 404!
The code on github: https://github.com/Teemitze/petstore
I build war file. Version spring 2.2.6.RELEASE
#Controller
#RequestMapping("/pets")
public class PetsController {
#Autowired
PetRepository petRepository;
#PostMapping("/addPet")
public void addPet(Pet pet) {
petRepository.save(pet);
}
#GetMapping
#ModelAttribute
public String pets(Model model) {
List<Pet> petList = new ArrayList<>();
petList.add(getPet());
petList.add(getPet());
petList.add(getPet());
model.addAttribute("pets", petList);
return "allPets";
}
public Pet getPet() {
Pet pet = new Pet();
pet.setId(1L);
pet.setName("Мурзик");
pet.setPrice(100);
pet.setBirthday(Date.valueOf("2019-12-12"));
pet.setSex("М");
return pet;
}
}
I checked out your code and found a few issues.
1) Package structure
Move controller, dto, repo packages to the main package (com.petstore)
Since the main application is inside the (com.petstore) package and the controller is outside the package, so it fails to scan the class.
2) Use annotation #Entity for the Pet entity class with #Id for the id property
3) Remove #ModelAttribute from pets() method since you are not binding any method parameter.
After this, I see the /pets
SpringBoot project requires define some configuration conventions that need to be follow in order to start a minimum application.
Some points you have to consider when you want to start a spring boot application.
For example:
Your SpringBootApplication(PetstoreApplication) class should be in the directory level above your other packages so that it can scan all classes.
If you want to use SpringData JPA you have to manage your model class
#Data
#Entity
public class Pet {
#Id
private long id;
private String name;
private String sex;
private Date birthday;
private byte[] photo;
private int price;
}
because it is handled by respository
public interface PetRepository extends CrudRepository<Pet, Long>
Need minimum configuration for Thymeleaf https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html
You are making a GET request for a resource "/pets" so no need #ModelAttribute in get mapping method
#GetMapping()
public String allPets(Model model) {
Make sure your html files is under resources/templates directory.
Check out the reference docs
spring mvc
spring data jpa

Spring Data REST - prevent property edit based on role

I use Spring Boot with Spring Data REST for data access and Spring Security for access restriction.
Assume I've got simple entity:
#Entity
public class Person {
#Id #GeneratedValue
private Long id;
private String firstName;
private Boolean isAuthorizedForClasifiedData;
}
I've got two roles in the application: USER and ADMIN.
Is there any simple way to prevent USER from changing value of isAuthorizedForClasifiedData while allowing ADMIN to update it?
The only solution I came up with is writing your own setter method.
public void setIsAuthorizedForClasifiedData(Boolean isAuthorizedForClasifiedData) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Optional<? extends GrantedAuthority> role_admin = authentication.getAuthorities().stream().filter(role -> role.getAuthority().equals("ROLE_ADMIN")).findAny();
role_admin.orElseThrow(() -> new YourOwnForbiddenException());
this.test = test;
}

Resources