How to pre-process values from spring configuration file? - java

I have a configuration parameter "myconfig.defaultSize" whose value is defined, for example, as "10MB", in the application.properties file.
In the other hand, I have a #Component class with #ConfigurationProperties annotation mapping those configuration parameter, as follows.
#Component
#ConfigurationProperties(prefix="myconfig")
public class StorageServiceProperties {
private Long defaultSize;
//...getters and setters
}
So, how can I would apply a method to convert the String value into Long?

You can't have such generic converter applied on a property-to-property basis. You could register a converter from String to Long but it would be called for every such case (any property of type Long basically).
The purpose of #ConfigurationProperties is to map the Environment to a higher-level data structure. Perhaps you could do that there?
#ConfigurationProperties(prefix="myconfig")
public class StorageServiceProperties {
private String defaultSize;
// getters and setters
public Long determineDefaultSizeInBytes() {
// parsing logic
}
}
If you look at the multipart support in Spring Boot, we keep the String value and we use the #ConfigurationProperties object to create a MultipartConfigElement that is responsible of the parsing. That way you can specify those special values in code and configuration.

public void setDefaultSize(String defaultSize) {
try {
this.defaultSize = Long.valueOf(defaultSize);
} catch (NumberFormatException e) {
// handle the exception however you like
}
}

Related

How to convert a Properties Object to Custom Object using Jackson?

In a Spring Boot application, Spring Boot is used to build a Properties object from a YAML file as follows:
YamlPropertiesFactoryBean yamlFactory = new YamlPropertiesFactoryBean();
yamlFactory.setResources(new DefaultResourceLoader().getResource("application.yml"));
Properties properties = yamlFactory.getObject();
The reason why Spring Boot's own parser is used is that it not only reads YAML-compliant settings, but also dot-notated properties like e.g:
artist.elvis.name: "Elvis"
artist.elvis.message: "Aloha from Hawaii"
Now that the Properties object is built, I want to map it into an object like the following for example:
#JsonIgnoreProperties(ignoreUnknown = true)
private record Artist(Elvis elvis) {
private record Elvis(String name, String message) { }
}
My question is:
How can this be done with Jackson? Or is there another/better solution for this?
Many thanks for any help
I saw functionality like that in Ratpack framework.
e.g.:
var propsFileUrl =
Thread.currentThread()
.getContextClassLoader()
.getResource("application.properties");
ApplicationProperties applicationProperties =
ConfigData.builder()
.props(propsFileUrl)
.build()
.get(ApplicationProperties.class);
under the hood it is indeed done by using jackson's object mapper, but the logic is not as trivial to post it here.
here's the library:
https://mvnrepository.com/artifact/io.ratpack/ratpack-core/2.0.0-rc-1
application.yml is the default yml file, so no custom configuration is required. Value annotation should be able to read the properties.
#Value("${artist.elvis.name}")
private String name;
Next part I am not sure about your requirements, but hope this is what you are looking for.
To bind to this object 'constructor' can be a good option.
Class for elvis
#Bean
public class Elvis {
private String name;
private String message;
public Elvis(#Value("${artist.elvis.name"}) final String name, #Value("${artist.elvis.message"}) final String message) {
this.name=name;
this.message=message
}
// getter setter for name and message
}
Now Autowire the created bean to Artist bean
#Bean("artists")
public class Artists {
#Autowired
private Elvis elvis
pubic Elvis getElvis() {
return elvis;
}
}

How to generate an example POJO from Swagger ApiModelProperty annotations?

We are creating a REST API which is documented using Swagger's #ApiModelProperty annotations. I am writing end-to-end tests for the API, and I need to generate the JSON body for some of the requests. Assume I need to post the following JSON to an endpoint:
{ "name": "dan", "age": "33" }
So far I created a separate class containing all the necessary properties and which can be serialized to JSON using Jackson:
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyPostRequest {
private String name;
private String age;
// getters and fluid setters omitted...
public static MyPostRequest getExample() {
return new MyPostRequest().setName("dan").setAge("33");
}
}
However, we noticed that we already have a very similar class in the codebase which defines the model that the API accepts. In this model class, the example values for each property are already defined in #ApiModelProperty:
#ApiModel(value = "MyAPIModel")
public class MyAPIModel extends AbstractModel {
#ApiModelProperty(required = true, example = "dan")
private String name;
#ApiModelProperty(required = true, example = "33")
private String age;
}
Is there a simple way to generate an instance of MyAPIModel filled with the example values for each property? Note: I need to be able to modify single properties in my end-to-end test before converting to JSON in order to test different edge cases. Therefore it is not sufficient to generate the example JSON directly.
Essentially, can I write a static method getExample() on MyAPIModel (or even better on the base class AbstractModel) which returns an example instance of MyAPIModel as specified in the Swagger annotations?
This does not seem to be possible as of the time of this answer. The closest possibilities I found are:
io.swagger.converter.ModelConverters: The method read() creates Model objects, but the example member in those models is null. The examples are present in the properties member in String form (taken directly from the APIModelParameter annotations).
io.swagger.codegen.examples.ExampleGenerator: The method resolveModelToExample() takes the output from ModelConverters.read(), and generates a Map representing the object with its properties (while also parsing non-string properties such as nested models). This method is used for serializing to JSON. Unfortunately, resolveModelToExample() is private. If it were publicly accessible, code to generate a model default for an annotated Swagger API model class might look like this:
protected <T extends AbstractModel> T getModelExample(Class<T> clazz) {
// Get the swagger model instance including properties list with examples
Map<String,Model> models = ModelConverters.getInstance().read(clazz);
// Parse non-string example values into proper objects, and compile a map of properties representing an example object
ExampleGenerator eg = new ExampleGenerator(models);
Object resolved = eg.resolveModelToExample(clazz.getSimpleName(), null, new HashSet<String>());
if (!(resolved instanceof Map<?,?>)) {
// Model is not an instance of io.swagger.models.ModelImpl, and therefore no example can be resolved
return null;
}
T result = clazz.newInstance();
BeanUtils.populate(result, (Map<?,?>) resolved);
return result;
}
Since in our case all we need are String, boolean and int properties, there is at least the possibility to parse the annotations ourselves in a crazy hackish manner:
protected <T extends MyModelBaseClass> T getModelExample(Class<T> clazz) {
try {
T result = clazz.newInstance();
for(Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(ApiModelProperty.class)) {
String exampleValue = field.getAnnotation(ApiModelProperty.class).example();
if (exampleValue != null) {
boolean accessible = field.isAccessible();
field.setAccessible(true);
setField(result, field, exampleValue);
field.setAccessible(accessible);
}
}
}
return result;
} catch (InstantiationException | IllegalAccessException e) {
throw new IllegalArgumentException("Could not create model example", e);
}
}
private <T extends MyModelBaseClass> void setField(T model, Field field, String value) throws IllegalArgumentException, IllegalAccessException {
Class<?> type = field.getType();
LOGGER.info(type.toString());
if (String.class.equals(type)) {
field.set(model, value);
} else if (Boolean.TYPE.equals(type) || Boolean.class.equals(type)) {
field.set(model, Boolean.parseBoolean(value));
} else if (Integer.TYPE.equals(type) || Integer.class.equals(type)) {
field.set(model, Integer.parseInt(value));
}
}
I might open an Issue / PR on Github later to propose adding functionality to Swagger. I am very surprised that nobody else has seemed to request this feature, given that our use case of sending exemplary model instances to the API as a test should be common.

Spring - Set property value using annotations without using properties file

I have a bean class like, for example
class Sample {
private String message;
public void setMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
and I want to set this property's value.
In Xml Configuration, I could do
<bean id = "sample" class = "Sample"
<property name = "message" value = "Hello there!"/>
</bean>
How do I achieve the same thing i.e. set property's value using Java Annotation? Now I have read that we can use #Value annotation using some properties file but cannot it be done without using properties file, doing the way I did it through xml file? Or using properties file is necessary?
I was able to do it by including #Value("Hello there!") above the setter method. But I could feel that is not a good idea. How to set the property values for different instances using Java Annotations?
Thanks.
The value inserted into the #Value can come from places other than a properties file, for example it can also use system properties.
Using the guide here as a starting point should help you understand a little better.
As a basic and mostly useless usage example we can only inject “string
value” from the annotation to the field:
#Value("string value")
private String stringValue;
Using the #PropertySource annotation allows us to work with values
from properties files with the #Value annotation. In the following
example we get “Value got from the file” assigned to the field:
#Value("${value.from.file}")
private String valueFromFile;
We can also set the value from system properties with the same syntax.
Let’s assume that we have defined a system property named systemValue
and look at the following sample:
#Value("${systemValue}")
private String systemValue;
Default values can be provided for properties that might not be
defined. In this example the value “some default” will be injected:
#Value("${unknown.param:some default}")
private String someDefault;
You have a few options, depending on your requirements. In both of these examples you can set the annotation on a setter instead of the field.
Custom PropertySource
This lets you continue using #Value with greater control for how the properties are supplied. There are a large number of PropertySource implementations, but you can always create your own.
References:
How to add custom property source to Spring's Environment
PropertySource JavaDoc
Example:
#Configuration
class MyConfiguration {
#Bean
PropertySource myPropertySource(ConfigurableEnvironment env) {
MapPropertySource source = new MapPropertySource("myPropertySource", singletonMap("myPropertyValue", "example"));
env.getPropertySources().addFirst(source);
return source;
}
}
class Sample {
#Value("${myPropertyValue}")
private String message;
public String getMessage() {
return message;
}
}
String Bean
Define a bean as a String and auto-wire it using its qualifier.
Example:
#Configuration
class MyConfiguration {
#Bean
String myPropertyValue() {
String value;
// do something to get the value
return value;
}
}
class Sample {
#Autowired
#Qualifier("myPropertyValue")
private String message;
public String getMessage() {
return message;
}
}

How to serialize differently the same properties of the same entity using jackson

Suppose you have this entity:
class Foo{
String propA;
String propB;
}
and you want to serialize for one API like :
{propA: "ola",
propB: "Holla"}
and for another API like :
{fooPropA: "ola",
fooPropB: "Holla"}
How can this be achieved using jackson and using the same entity. Creating 2 different entities is not an option :)
There are several ways in which you can achieve this. You can enable a custom serializer (already covered by #se_vedem), register an annotation introspector which changes the property names for the corresponding class and so on.
However, if you are willing to only add a string prefix to all the property names, then the Jackson property name strategy is probably the best fit. The naming strategy class has the access to the serialized object type information, so you can make a decision whether to change the property name or not.
Here is an example using a custom annotation that defines the prefix:
public class JacksonNameStrategy {
#Retention(RetentionPolicy.RUNTIME)
public static #interface PropertyPrefix {
String value();
}
#PropertyPrefix("foo_")
public static class Foo {
public String propA;
public String propB;
public Foo(String propA, String propB) {
this.propA = propA;
this.propB = propB;
}
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new MyPropertyNamingStrategyBase());
System.out.println(mapper.writeValueAsString(new Foo("old", "Holla")));
}
private static class MyPropertyNamingStrategyBase extends PropertyNamingStrategy {
#Override
public String nameForField(MapperConfig<?> config,
AnnotatedField field,
String defaultName) {
PropertyPrefix ann = field.getDeclaringClass().getAnnotation(PropertyPrefix.class);
if (ann != null) {
return ann.value() + defaultName;
}
return super.nameForField(config, field, defaultName);
}
}
}
Output:
{"foo_propA":"old","foo_propB":"Holla"}
In your API method you choose between two ObjectMapper instances one with the default naming naming strategy and one with the custom one.
You can achieve this by using modules feature from Jackson.
Basically, each API would have it's own ObjectMapper and they will be configured with different modules. This way you can create 2 serializers for the same class and register them on the appropriate module. More read can be found here http://wiki.fasterxml.com/JacksonFeatureModules
However, be aware that serializers are loaded in a particular order. First it tries to get the annotated ones, if none is found it will try to get those registered from modules. So, for example if you have your class annotated with serializer, then that serializer(FooSerializer) would be chosen instead of the one configured in module(MySecondFooSerializer).
#JsonSerialize(using = FooSerializer.class)
class Foo{
String propA;
String propB;
}
module.addSerializer(Foo.class, new MySecondFooSerializer());

Extract annotation parameters from a bean/class

I am using the #JsonProperty(name = "property_name") (Jackson) annotation wherever the property name in the json is different from the property name in my object. Is there a way I can programmatically access this information somewhere else in code?
Example:
public class Entity {
protected long entityName;
#JsonProperty("entity_name")
public long getEntityName() {
return entityName;
}
public void setEntityName(String entityName) {
this.entityName = entityName;
}
}
What I need is a method to map entity_name to entityName. e.g, getActualFieldName("entity_name") should return "entityName". I can keep a separate file with all these mappings, but I don't want to do that because this information is already present in the form of #JsonProperty annotations. If I can extract it from there somehow, it would simplify things a bit.
Check this Helper utility. getAnnotationParameter

Categories

Resources