How to bind a Map object containing Pojos to a yaml file? - java

In a spring-boot/ spring-cloud application, I would like to bind a Map object to my application.yml but I've got a "Elements ... where lef unbound error".
In my class called Ebox, I would like to bind a map called infosTenants, indentified by a string and containing values of type InfosTenant.
Below my application.yml (without the getters / setters of each classes or subclasses)
#ConfigurationProperties(prefix = "application", ignoreUnknownFields = false)
public class ApplicationProperties {
private Ebox ebox = new Ebox();
public ApplicationProperties() {
}
// getters/setters ...
public static class Ebox {
private String authUrl;
private Map<String, InfosTenant> infosTenants = new HashMap<>();
public Ebox() {
}
public class InfosTenant{
private String clientId="";
private String clientSecret="";
public InfosTenant() {
}
// getters/setters ...
}
}
}
In my application.yml, I defined one tenant in my tenants map, indentified by the key tenant1.
application:
ebox:
auth-url: https://oauth-server/api/oauth/token
infos-tenants:
tenant1:
client-id: myclient
client-secret: secret
But all values under infos-tenants were left unbound.
Does somebody have an idea ?
Thanks

I found my error, inner classes should be static, I forgot the static before class InfosTenant.
public static class InfosTenant{
private String clientId="";
private String clientSecret="";
public InfosTenant() {
}
// getters/setters ...
}

Related

JAVA - Using same POJO but with less fields (without jackson)

I have a POJO used by an existing endpoint, this endpoint responds with all fields present in the POJO.
But I'm creating a new enpoint which should respond only some of the fields of the same POJO.
I want to avoid copying the same POJO file and deleting the parameters I don't need, is there a way to do this?
This is the POJO:
public class AgentChatStatus {
private UUID activeChat;
private List<AgentChat> chatRequests; //Object with less params on new endpoint
private List<AgentChat> chatsOnHold; //Object with less params on new endpoint
private Collection<Agent> agents;
private int totalChatRequests;
private int totalChatsOnHold;
private Preferences.AgentConsoleConfig config;
// ...
}
public class AgentChat implements Payload {
private UUID id;
private String queueId;
Lets say I only need to show "Id" in endpoint 2 but id and queueId in endpoint1.
I work with spring btw.
Thanks!
With Jackson you can take advantage of JSON Views. The first thing is to create the Views as follows:
public class Views {
public static class ViewEndpoint2 { // The name can be whatever you want
}
public static class ViewEndpoint1 extends ViewEndpoint2 {
}
}
Then you need to annotate the properties in your POJO with #JsonView so that you tell Jackson in which views such properties should be visible:
public class AgentChatStatus {
#JsonView(Views.ViewEndpoint2.class)
private UUID activeChat;
#JsonView(Views.ViewEndpoint2.class)
private List<AgentChat> chatRequests;
#JsonView(Views.ViewEndpoint2.class)
private List<AgentChat> chatsOnHold;
#JsonView(Views.ViewEndpoint2.class)
private Collection<Agent> agents;
#JsonView(Views.ViewEndpoint2.class)
private int totalChatRequests;
#JsonView(Views.ViewEndpoint2.class)
private int totalChatsOnHold;
#JsonView(Views.ViewEndpoint2.class)
private Preferences.AgentConsoleConfig config;
}
public class AgentChat implements Payload {
#JsonView(Views.ViewEndpoint2.class)
private UUID id;
#JsonView(Views.ViewEndpoint1.class)
private String queueId;
}
Finally, you need to annotate the endpoints with the corresponding view:
#JsonView(Views.ViewEndpoint1.class)
#RequestMapping("your-old-enpoint")
public void yourOldEndpoint() {
(...)
}
#JsonView(Views.ViewEndpoint2.class)
#RequestMapping("your-new-enpoint")
public void yourNewEndpoint() {
(...)
}
#JsonView(Views.ViewEndpoint1.class) basically means all properties in AgentChatStatus and AgentChat and #JsonView(Views.ViewEndpoint2.class) means only some of them (the ones annotated with #JsonView(Views.ViewEndpoint2.class)).
You can read more about this at https://www.baeldung.com/jackson-json-view-annotation.

Handling multiple same properties with #ConfigurationProperties

I have Properties class with a few different beans. Values from application.yaml:
#Configuration
#Getter
#Setter
public class RabbitProperties {
private String requestExchangeName;
private String requestQueueName;
private String responseExchangeName;
private String deadLetterExchangeName;
#Bean
#ConfigurationProperties("rabbit-service.common-orders")
public RabbitProperties commonOrdersRabbitProperties() {
return new RabbitProperties();
}
#Bean
#ConfigurationProperties("rabbit-service.metrics")
public RabbitProperties metricsRabbitProperties() {
return new RabbitProperties();
}
...//more beans
}
I'm using this Configuration in another config class:
#Configuration
#RequiredArgsConstructor
public class RabbitServiceConfig {
private final RabbitProperties commonOrdersRabbitProperties;
private final RabbitProperties metricsRabbitProperties;
...//about 15 similar fields
#Bean("metricsRabbitService")
public RabbitService getMetricsRabbitService(AmqpAdmin amqpAdmin, Client rabbitClient) {
return new RabbitService(
metricsRabbitProperties.getRequestExchangeName(),
metricsRabbitProperties.getRequestQueueName(),
metricsRabbitProperties.getResponseExchangeName(),
metricsRabbitProperties.getDeadLetterExchangeName(),
rabbitClient,
amqpAdmin
);
}
#Bean("commonOrdersRabbitService")
public RabbitService getCommonOrdersRabbitService(AmqpAdmin amqpAdmin, Client rabbitClient) {
return new RabbitService(
commonOrdersRabbitProperties.getRequestExchangeName(),
commonOrdersRabbitProperties.getRequestQueueName(),
commonOrdersRabbitProperties.getResponseExchangeName(),
commonOrdersRabbitProperties.getDeadLetterExchangeName(),
rabbitClient,
amqpAdmin
);
}
...//etc
I'm adding new RabbitProperties field almost every week, so now it already has about 15-20 kinda same fields. How can I get rid of these fields and put them to Map for example? Where should I put values for this Map and initialize it? What is the proper way to use ConfigurationProperties here?
If I've understood the question properly, you could define a private final Map<String, RabbitProperties> rabbitPropertiesMap; in the RabbitServiceConfig class instead of all the fields. All the RabbitProperties will be bounded in the map by injection, with key equals to bean name.
Another different approach would be to update the implementation of RabbitProperties with something like
#ConfigurationProperties("rabbit-service")
#Value
#ConstructorBinding
public class RabbitServiceProperties {
Map<String, RabbitProperties> rabbitPropertiesMap;
#Value
static class RabbitProperties {
String requestExchangeName;
String requestQueueName;
String responseExchangeName;
String deadLetterExchangeName;
}
}
this way everything under the root rabbit-service in the application.yml with the structure described will be discovered and bound.

Env variables in data class in Redis model annotation

Troubling to specify dynamic env variable in the model class #Hashkey Redis annotation.
Model:
#RedisHash("${spring.redis.namespace}:Book")
public class Book {
#Id
private String id;
private String name;
}
My application.properties file:
spring.redis.namespace=local
The resulting key is "${spring.redis.namespace}:Book" instead of local:Book
Could anyone help me with this?
Please use Keyspaces to do it. There two ways. I use one way to finish your requirement.
#Configuration
#EnableRedisRepositories(keyspaceConfiguration = MyKeyspaceConfiguration.class)
public class ApplicationConfig {
#Value("${spring.redis.namespace}:Book")
String myKey;
//... RedisConnectionFactory and RedisTemplate Bean definitions omitted
public static class MyKeyspaceConfiguration extends KeyspaceConfiguration {
#Override
protected Iterable<KeyspaceSettings> initialConfiguration() {
return Collections.singleton(new KeyspaceSettings(Book.class, myKey));
}
}
}

Configuring an enum in Spring using application.properties

I have the following enum:
public enum MyEnum {
NAME("Name", "Good", 100),
FAME("Fame", "Bad", 200);
private String lowerCase;
private String atitude;
private long someNumber;
MyEnum(String lowerCase, String atitude, long someNumber) {
this.lowerCase = lowerCase;
this.atitude = atitude;
this.someNumber = someNumber;
}
}
I want to setup the someNumber variable different for both instances of the enum using application.properties file.
Is this possible and if not, should i split it into two classes using an abstract class/interface for the abstraction?
You can't/shouldn't change values of a enum in Java. Try using a class instead:
public class MyCustomProperty {
// can't change this in application.properties
private final String lowerCase;
// can change this in application.properties
private String atitude;
private long someNumber;
public MyCustomProperty (String lowerCase, String atitude, long someNumber) {
this.lowerCase = lowerCase;
this.atitude = atitude;
this.someNumber = someNumber;
}
// getter and Setters
}
Than create a custom ConfigurationProperties:
#ConfigurationProperties(prefix="my.config")
public class MyConfigConfigurationProperties {
MyCustomProperty name = new MyCustomProperty("name", "good", 100);
MyCustomProperty fame = new MyCustomProperty("fame", "good", 100);
// getter and Setters
// You can also embed the class MyCustomProperty here as a static class.
// For details/example look at the linked SpringBoot Documentation
}
Now you can change the values of my.config.name.someNumber and my.config.fame.someNumber in the application.properties file. If you want to disallow the change of lowercase/atitude make them final.
Before you can use it you have to annotate a #Configuration class with #EnableConfigurationProperties(MyConfigConfigurationProperties.class). Also add the org.springframework.boot:spring-boot-configuration-processor as an optional dependency for a better IDE Support.
If you want to access the values:
#Autowired
MyConfigConfigurationProperties config;
...
config.getName().getSumeNumber();
Well what you can do is the following:
Create a new class: MyEnumProperties
#ConfigurationProperties(prefix = "enumProperties")
#Getter
public class MyEnumProperties {
private Map<String, Long> enumMapping;
}
Enable ConfigurationProperties to your SpringBootApplication/ any Spring Config via
#EnableConfigurationProperties(value = MyEnumProperties.class)
Now add your numbers in application.properties file like this:
enumProperties.enumMapping.NAME=123
enumProperties.enumMapping.FAME=456
In your application code autowire your properties like this:
#Autowired
private MyEnumProperties properties;
Now here is one way to fetch the ids:
properties.getEnumMapping().get(MyEnum.NAME.name()); //should return 123
You can fetch this way for each Enum value the values defined in your application.properties

Java Object mapping framework working with builder pattern

Is there any class mapping framework which works with builders? I would like to keep some of my classes immutable and avoid multiple constructors - the Builder Pattern comes to the rescue. However I can't any mapping framework which would use builder automatically instead of getters/setters.
I got the following working with Lombok and ModelMapper. See: http://modelmapper.org/getting-started/
public class MyService {
private ModelMapper modelMapper;
public MyService(){
this.modelMapper = new ModelMapper();
this.modelMapper.getConfiguration()
.setMatchingStrategy(MatchingStrategies.STRICT)
.setDestinationNamingConvention(LombokBuilderNamingConvention.INSTANCE)
.setDestinationNameTransformer(LombokBuilderNameTransformer.INSTANCE);
}
public OutputDTO aMethod(final InputDTO input){
return modelMapper.map(input, OutputDTO.OutputDTOBuilder.class).build();
}
}
Where LombokBuilderNamingConvention is:
import org.modelmapper.spi.NamingConvention;
import org.modelmapper.spi.PropertyType;
public class LombokBuilderNamingConvention implements NamingConvention {
public static LombokBuilderNamingConvention INSTANCE = new LombokBuilderNamingConvention();
#Override
public boolean applies(String propertyName, PropertyType propertyType) {
return PropertyType.METHOD.equals(propertyType);
}
#Override
public String toString() {
return "Lombok #Builder Naming Convention";
}
}
And LombokBuilderNameTransformer is:
import org.modelmapper.spi.NameTransformer;
import org.modelmapper.spi.NameableType;
public class LombokBuilderNameTransformer implements NameTransformer {
public static final NameTransformer INSTANCE = new LombokBuilderNameTransformer();
#Override
public String transform(final String name, final NameableType nameableType) {
return Strings.decapitalize(name);
}
#Override
public String toString() {
return "Lombok #Builder Mutator";
}
}
And OutputDTO can look like:
#Builder // Has .builder() static method
#Value // Thus immutable
public class OutputDTO {
private String foo;
private int bar;
}
This can be easily done with MapStruct and using a custom naming strategy for builders.
Have a look here in the documentation how to use Custom Accessor naming strategy.
Your mappings then need to look like:
#Mapper
public interface MyMapper {
default Immutable map(Source source) {
return mapToBuilder(source).build();
}
Immutable.Builder mapToBuilder(Source source);
}
Within MapStruct we are already working on a feature that would support out of the box support for builders. You can follow this issue for more details.
Update
MapStruct now (since 1.3.0.Beta1) has out of the box support for Immutables. This means that the mapper before can be written like:
#Mapper
public interface MyMapper {
Immutable map(Source source);
}
The assumption is that there is a public static method without parameters in Immutable that returns the builder
Uing Lombok and ModelMapper configure as:
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
.setFieldMatchingEnabled(true)
.setFieldAccessLevel(AccessLevel.PRIVATE);
By default ModelMapper uses only public setter method to map. When the class annotated with Lombok builder annotation it made the setter method as private. So to allow the ModelMapper to use the private setter method we need to add the above configureation.
OR
Configuration builderConfiguration = modelMapper.getConfiguration().copy()
.setDestinationNameTransformer(NameTransformers.builder())
.setDestinationNamingConvention(NamingConventions.builder());
modelMapper.createTypeMap(MyEntity.class, MyDto.MyDtoBuilder.class, builderConfiguration);
where MyEnity class is:
#Data
private static class MyEntity {
private Long id;
private String name;
private String value;
}
and builder class is:
#Data
#Builder
private static class MyDto {
private final Long id;
private final String name;
private final String value;
}
click here for detail

Categories

Resources