Reading all variables from application properties using single class - java

I have a Spring Boot application with the below application.properties:
message=default1
security.url=defaultSecurityURL.com
security.authMethod=defaultAuth
security.roles=USER,ADMIN,PARTNER
message_temp=dummyvaluehere
Now, I have a Java class that reads all these variables from the application.properties:
#ConfigurationProperties
#ConstructorBinding
public final class EnvConfig {
private final String message;
private final Security security; //there is a Security class defined seperately
private final String messageTemp;
public EnvConfig(String message, Security security, String messageTemp) {
super();
this.message = message;
this.security = new Security(security.getUrl(), security.getAuthMethod(), security.getRoles()); //this is done for immutability
this.messageTemp = messageTemp;
}
public String getMessage() {
return message;
}
public Security getSecurity() {
return security;
}
public String getMessageTemp() {
return messageTemp;
}
#Override
public String toString() {
return "EnvConfig [message=" + message + ", security=" + security + ", messageTemp="
+ messageTemp + "]";
}
}
Everything works fine. Now I want to add 2 more variables to my application.properties file:
message.default.app.deploy.strategy=bg
message.default.app.deploy.retries=3
But I don't want to read these variables by declaring a new class (like I created a Security class above). How can I read these values in my EnvConfig class?

Look in my answer of this SO question which offers a generic way to access all variables of the spring environment (by its string key).

Related

How to set default values of model class variables from yaml file?

In a service file I would simply use #Value and initialize the variable instially there. I have tried this approach in a model class but (I assume how things get autowired and that its a model class) this results in it always being null.
The need for this comes out that in different environments the default value is always different.
#Value("${type}")
private String type;
I would avoid trying to use Spring logic inside the models as they are not Spring beans themselves. Maybe use some form of a creational (pattern) bean in which the models are constructed, for example:
#Component
public class ModelFactory {
#Value("${some.value}")
private String someValue;
public SomeModel createNewInstance(Class<SomeModel> clazz) {
return new SomeModel(someValue);
}
}
public class SomeModel {
private String someValue;
public SomeModel(String someValue) {
this.someValue = someValue;
}
public String getSomeValue() {
return someValue;
}
}
#ExtendWith({SpringExtension.class})
#TestPropertySource(properties = "some.value=" + ModelFactoryTest.TEST_VALUE)
#Import(ModelFactory.class)
class ModelFactoryTest {
protected static final String TEST_VALUE = "testValue";
#Autowired
private ModelFactory modelFactory;
#Test
public void test() {
SomeModel someModel = modelFactory.createNewInstance(SomeModel.class);
Assertions.assertEquals(TEST_VALUE, someModel.getSomeValue());
}
}

How to call feild name in postman request body defined in application.properties

I have declared two enums in two separate application.properties file and I have also created a class for constant values.
Application.properties file1
EnumProperty.Provider=Provider1
url1=http://localhost:8080/some/url
Application.properties file2
EnumProperties.Provider=Provider2
url1=http://localhost:8080/some/urlss
CommonConstant.class
public final String PROVIDER1 = "PROVIDER1";
public final String PROVIDER2="PROVIDER2";
Also I have ConfigurationReader.class
#Value("{EnumProperty.Provider}")
private String providerOne;
#Value("{EnumProperties.Provider}")
private String providerTwo;
Now I have a service class where I'm suppose to call one class out of two based on which provider I'm calling.Which is as follows
public ResponseObject service(CommonRequestFeilds commonRequestFeilds) {
if (configurationReader.getProviderOne().equals(CommonConstant.PROVIDER1)) {
classOneServiceImp.someMethodOfClass1(commonRequestFeilds);
}
else if (configurationReader.getProviderTwo().equals(CommonConstant.PROVIDER2)) {
classTwoServiceImp.someMethodOfClass2(commonRequestFeilds);
}
return null;
}
Also CommonRequestFeild.class
private String email;
private String firstName;
private String lastName;
Postman RequestBody
{
"email":"john#example.com",
"firstName":"John",
"lastName":"Doe",
}
Now in postman request body I'm sending the commonRequestFeilds values.But where I'm stuck is how do I tell in my postman request body which provider I'm trying to call from ConfigurationReader class.Should I declare ConfigurationReader class in CommonRequestFeild class? Please help.

How to programmatically replace Spring's NumberFormatException with a user-friendly text?

I am working on a Spring web app and i have an entity that has an Integer property which the user can fill in when creating a new entity using a JSP form. The controller method called by this form is below :
#RequestMapping(value = {"/newNursingUnit"}, method = RequestMethod.POST)
public String saveNursingUnit(#Valid NursingUnit nursingUnit, BindingResult result, ModelMap model)
{
boolean hasCustomErrors = validate(result, nursingUnit);
if ((hasCustomErrors) || (result.hasErrors()))
{
List<Facility> facilities = facilityService.findAll();
model.addAttribute("facilities", facilities);
setPermissions(model);
return "nursingUnitDataAccess";
}
nursingUnitService.save(nursingUnit);
session.setAttribute("successMessage", "Successfully added nursing unit \"" + nursingUnit.getName() + "\"!");
return "redirect:/nursingUnits/list";
}
The validate method simply checks if the name already exists in the DB so I did not include it. My issue is that, when I purposely enter text in the field, I would like to have a nice message such as "The auto-discharge time must be a number!". Instead, Spring returns this absolutely horrible error :
Failed to convert property value of type [java.lang.String] to required type [java.lang.Integer] for property autoDCTime; nested exception is java.lang.NumberFormatException: For input string: "sdf"
I fully understand why this is happening but i cannot for the life of me figure out how to, programmatically, replace Spring's default number format exception error message with my own. I am aware of message sources which can be used for this type of thing but I really want to achieve this directly in the code.
EDIT
As suggested, i built this method in my controller but i'm still getting Spring's "failed to convert property value..." message :
#ExceptionHandler({NumberFormatException.class})
private String numberError()
{
return "The auto-discharge time must be a number!";
}
OTHER EDIT
Here is the code for my entity class :
#Entity
#Table(name="tblNursingUnit")
public class NursingUnit implements Serializable
{
private Integer id;
private String name;
private Integer autoDCTime;
private Facility facility;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId()
{
return id;
}
public void setId(Integer id)
{
this.id = id;
}
#Size(min = 1, max = 15, message = "Name must be between 1 and 15 characters long")
#Column(nullable = false, unique = true, length = 15)
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
#NotNull(message = "The auto-discharge time is required!")
#Column(nullable = false)
public Integer getAutoDCTime()
{
return autoDCTime;
}
public void setAutoDCTime(Integer autoDCTime)
{
this.autoDCTime = autoDCTime;
}
#ManyToOne (fetch=FetchType.EAGER)
#NotNull(message = "The facility is required")
#JoinColumn(name = "id_facility", nullable = false)
public Facility getFacility()
{
return facility;
}
public void setFacility(Facility facility)
{
this.facility = facility;
}
#Override
public boolean equals(Object obj)
{
if (obj instanceof NursingUnit)
{
NursingUnit nursingUnit = (NursingUnit)obj;
if (Objects.equals(id, nursingUnit.getId()))
{
return true;
}
}
return false;
}
#Override
public int hashCode()
{
int hash = 3;
hash = 29 * hash + Objects.hashCode(this.id);
hash = 29 * hash + Objects.hashCode(this.name);
hash = 29 * hash + Objects.hashCode(this.autoDCTime);
hash = 29 * hash + Objects.hashCode(this.facility);
return hash;
}
#Override
public String toString()
{
return name + " (" + facility.getCode() + ")";
}
}
YET ANOTHER EDIT
I am able to make this work using a message.properties file on the classpath containing this :
typeMismatch.java.lang.Integer={0} must be a number!
And the following bean declaration in a config file :
#Bean
public ResourceBundleMessageSource messageSource()
{
ResourceBundleMessageSource resource = new ResourceBundleMessageSource();
resource.setBasename("message");
return resource;
}
This gives me the correct error message instead of the Spring generic TypeMismatchException / NumberFormatException which i can live with but still, I want to do everything programmatically wherever possible and I'm looking for an alternative.
Thank you for your help!
You may be able to override that messaging by providing an implementation of the Spring DefaultBindingErrorProcessor similar to what is done here:
Custom Binding Error Message with Collections of Beans in Spring MVC
You can annotate a method with:
#ExceptionHandler({NumberFormatException.class})
public String handleError(){
//example
return "Uncorrectly formatted number!";
}
and implement whatever you want to do in case the exception of that type is thrown. The given code will handle exceptions happened in the current controller.
For further reference consult this link.
To make global error handling you can use #ControllerAdvice in the following way:
#ControllerAdvice
public class ServiceExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({NumberFormatException.class})
public String handleError(){
//example
return "Uncorrectly formatted number!";
}
}
#Martin, I asked you about the version because #ControllerAdvice is available starting with version 3.2.
I would recommend you to use #ControllerAdvice, which is an annotation that allows you to write code that is sharable between controllers(annotated with #Controller and #RestController), but it can also be applied only to controllers in specific packages or concrete classes.
ControllerAdvice is intended to be used with #ExceptionHandler, #InitBinder, or #ModelAttribute.
You set the target classes like this #ControllerAdvice(assignableTypes = {YourController.class, ...}).
#ControllerAdvice(assignableTypes = {YourController.class, YourOtherController.class})
public class YourExceptionHandler{
//Example with default message
#ExceptionHandler({NumberFormatException.class})
private String numberError(){
return "The auto-discharge time must be a number!";
}
//Example with exception handling
#ExceptionHandler({WhateverException.class})
private String whateverError(WhateverException exception){
//do stuff with the exception
return "Whatever exception message!";
}
#ExceptionHandler({ OtherException.class })
protected String otherException(RuntimeException e, WebRequest request) {
//do stuff with the exception and the webRequest
return "Other exception message!";
}
}
What you need to keep in mind is that if you do not set the target and you define multiple exception handlers for the same exceptions in different #ControllerAdvice classes, Spring will apply the first handler that it finds. If multiple exception handlers are present in the same #ControllerAdvice class, an error will be thrown.
Solution 1: StaticMessageSource as Spring bean
This gives me the correct error message instead of the Spring generic TypeMismatchException / NumberFormatException which i can live with but still, I want to do everything programmatically wherever possible and I'm looking for an alternative.
Your example uses ResourceBundleMessageSource which uses resource bundles (such as property files). If you want to use everything programmatically, then you could use a StaticMessageSource instead. Which you can then set as a Spring bean named messageSource. For example:
#Configuration
public class TestConfig {
#Bean
public MessageSource messageSource() {
StaticMessageSource messageSource = new StaticMessageSource();
messageSource.addMessage("typeMismatch.java.lang.Integer", Locale.getDefault(), "{0} must be a number!");
return messageSource;
}
}
This is the simplest solution to get a user friendly message.
(Make sure the name is messageSource.)
Solution 2: custom BindingErrorProcessor for initBinder
This solution is lower level and less easy than solution 1, but may give you more control:
public class CustomBindingErrorProcessor extends DefaultBindingErrorProcessor {
public void processPropertyAccessException(PropertyAccessException ex, BindingResult bindingResult) {
Throwable cause = ex.getCause();
if (cause instanceof NumberFormatException) {
String field = ex.getPropertyName();
Object rejectedValue = ex.getValue();
String[] codes = bindingResult.resolveMessageCodes(ex.getErrorCode(), field);
Object[] arguments = getArgumentsForBindError(bindingResult.getObjectName(), field);
boolean useMyOwnErrorMessage = true; // just so that you can easily see to default behavior one line below
String message = useMyOwnErrorMessage ? field + " must be a number!" : ex.getLocalizedMessage();
FieldError error = new FieldError(bindingResult.getObjectName(), field, rejectedValue, true, codes, arguments, message);
error.wrap(ex);
bindingResult.addError(error);
} else {
super.processPropertyAccessException(ex, bindingResult);
}
}
}
#ControllerAdvice
public class MyControllerAdvice {
#InitBinder
public void initBinder(WebDataBinder binder) {
BindingErrorProcessor customBindingErrorProcessor = new CustomBindingErrorProcessor();
binder.setBindingErrorProcessor(customBindingErrorProcessor);
}
}
It basically intercepts the call to DefaultBindingErrorProcessor.processPropertyAccessException and adds a custom FieldError message when binding failed with a NumberFormatException.
Example code without Spring Web/MVC
In case you want to try it without Spring Web/MVC, but just plain Spring, then you could use this example code.
public class MyApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
Validator validator = context.getBean(LocalValidatorFactoryBean.class);
// Empty person bean to be populated
Person2 person = new Person2(null, null);
// Data to be populated
MutablePropertyValues propertyValues = new MutablePropertyValues(List.of(
new PropertyValue("name", "John"),
// Bad value
new PropertyValue("age", "anInvalidInteger")
));
DataBinder dataBinder = new DataBinder(person);
dataBinder.setValidator(validator);
dataBinder.setBindingErrorProcessor(new CustomBindingErrorProcessor());
// Bind and validate
dataBinder.bind(propertyValues);
dataBinder.validate();
// Get and print results
BindingResult bindingResult = dataBinder.getBindingResult();
bindingResult.getAllErrors().forEach(error ->
System.out.println(error.getDefaultMessage())
);
// Output:
// "age must be a number!"
}
}
#Configuration
class MyConfig {
#Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
}
class Person2 {
#NotEmpty
private String name;
#NotNull #Range(min = 20, max = 50)
private Integer age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public Person2(String name, Integer age) {
this.name = name;
this.age = age;
}
}
Handle NumberFormatException.
try {
boolean hasCustomErrors = validate(result, nursingUnit);
}catch (NumberFormatException nEx){
// do whatever you want
// for example : throw custom Exception with the custom message.
}

Spring Boot Configuration Properties not being set

I am trying to set up a clamav virus scanner in a Spring Boot environment. So I want to set the host and port in a properties file, clamav.properties, located in my resources directory along with the application.properties file. it looks like this:
clamav.host=localhost
clamav.port=3310
clamav.timeout=1000
I have this class:
#ConfigurationProperties("clamav.properties")
public class ClamAvClient {
static final Logger logger = LoggerFactory.getLogger(ClamAvClient.class);
#Value("${clamav.host}")
private String clamHost;
#Value("${clamav.port}")
private int clamPort;
#Value("${clamav.timeout}")
private int clamTimeout;
public boolean ping() throws IOException {
logger.debug("Host:"+clamHost+" Port:"+clamPort);
blah.....
}
private static byte[] asBytes(String s) {
return s.getBytes(StandardCharsets.US_ASCII);
}
public String getClamHost() {
return clamHost;
}
public void setClamHost(String clamHost) {
this.clamHost = clamHost;
}
public int getClamPort() {
return clamPort;
}
public void setClamPort(int clamPort) {
this.clamPort = clamPort;
}
public int getClamTimeout() {
return clamTimeout;
}
public void setClamTimeout(int clamTimeout) {
this.clamTimeout = clamTimeout;
}
}
It's not connecting and in the logs I get this:
2017-09-23 20:39:45.947 DEBUG 28857 --- [http-nio-8080-exec-2] xxx.ClamAvClient : Host:null Port:0
So those values are clearly not getting set. What am I doing wrong? I am using the managed version of spring-boot-starter-web, which my Eclipse is saying is 1.4.3-RELEASE
Any Ideas?
Either use #ConfigurationProperties to map group of properties to a Class using Configuration Processor.
Using #Value inside #ConfigurationProperties doesn`t look right.
All you need to map your properties to the class is :
#Configuration
#ConfigurationProperties(prefix="clamav")
public class ClamAvClient {
static final Logger logger = LoggerFactory.getLogger(ClamAvClient.class);
private String host;
private int port;
private int timeout;
//getters and setters
}
prefix ="clamav" matches your prefixes in the properties file.
host,port,timeout matches the properties of the class.

java - Autowiring Repository in class that is instantiated in Spring Boot

So I have an API client type class right now, which I am trying to connect to my repository so that I can store data in the MySQL database.
The problem I'm having is that the API client class instantiates a new object of itself, so the Autowiring doesn't work correctly. I've looked around for a workaround for this problem, and I've seen a couple options, but I'm confused on how to apply them to my problem.
For reference, here are parts of some of the relevant files:
GeniusApiClient.java:
#Component
public final class GeniusApiClient {
private final OkHttpClient client = new OkHttpClient();
#Autowired
private ArtistDao artistDao;
public static void main(String[] args) throws Exception {
GeniusApiClient geniusApiClient = new GeniusApiClient();
String artistId = (geniusApiClient.getArtistId("Ugly Duckling"));
ArrayList<String> artistSongIds = geniusApiClient.getArtistSongIds(artistId);
System.out.println(geniusApiClient.getAllSongAnnotations(artistSongIds, artistId));
}
public String getAllSongAnnotations(ArrayList<String> songIds, String artistId) {
Artist artist = new Artist("test name for now", "string123", "223");
artistDao.save(artist);
return "finished";
}
}
ArtistDao.java:
#Transactional
public interface ArtistDao extends CrudRepository<Artist, Long> {
public Artist findByGeniusId(String geniusId);
}
ArtistController.java:
#Controller
public class ArtistController {
#Autowired
private ArtistDao artistDao;
/**
* GET /create --> Create a new artist and save it in the database.
*/
#RequestMapping("/create")
#ResponseBody
public String create(String name, String annotations, String genius_id) {
String userId = "";
try {
genius_id = genius_id.replaceAll("/$", "");
Artist artist = new Artist(name, annotations, genius_id);
artistDao.save(artist);
userId = String.valueOf(artist.getId());
}
catch (Exception ex) {
return "Error creating the artist: " + ex.toString();
}
return "User succesfully created with id = " + userId;
}
/**
* GET /get-by-email --> Return the id for the user having the passed
* email.
*/
#RequestMapping("/get")
#ResponseBody
public String getByEmail(String genius_id) {
String artistId = "";
try {
Artist artist = artistDao.findByGeniusId(genius_id);
artistId = String.valueOf(artist.getId());
}
catch (Exception ex) {
return "User not found";
}
return "The user id is: " + artistId;
}
}
The problem is that in GeniusApiClient.java in the getAllSongAnnotations method, I have a null pointer exception when I try and access the artistDao. I understand that my instantiation of this class is what is messing up the Autowiring, but I'm curious on what the best way to go about fixing this might be.
I considered making all of my methods in the class static so that I wouldn't have to instantiate a new method, but I don't think this would work very well. Any suggestions?
Thanks
EDIT:
Removed some irrelevant code for clarity.
EDIT2:
Added ArtistController.java
To be able to autowire/inject an object, that object must be a Spring bean.
Here you can't autowire ArtistDao because it's not a bean. There are several annotation options to make it bean but the one suits in this case is #Repository annotation. It's just a specialized version of #Component which you used in GeniusApiClient class.
So,
#Repository
#Transactional
public interface ArtistDao extends CrudRepository<Artist, Long> {
public Artist findByGeniusId(String geniusId);
}
should work.
I'd suggest you to read: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html
If reading reference documentation sounds scary to you, you can also take a look at Core Spring part of Spring in Action.
Don't make GeniusApiClient.class final. Spring will use CGLIB to dynamically extend your class in order to make a proxy. And the requirement for CGLIB to work is to have your classes non-final.
More on this here: Make Spring Service Classes Final?
What you are trying to do in your catch block is not clear to me,you have to correct that and replace it with desired action to be taken on any exception occurrence.

Categories

Resources