How to map configuration objects to java object - spring-boot

I have a spring boot application which is using a spring cloud config.
How can i map a configuration element with some java object.
My config is something like this:
clients:
- id : 1
name: client 1
groups : [a,b]
- id : 2
name: client 2
groups : [a]
And my java object is:
public class ClientInfo {
private String clientId;
private List<String> profiles;
public ClientInfo(String clientId, List<String> pips) {
this.clientId = clientId;
this.profiles = pips;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public List<String> getProfiles() {
return profiles;
}
public void setProfiles(List<String> profiles) {
this.profiles = profiles;
}
}
I want to map my configuration with List

Use below code to configure configuration properties in to java Object,
#Component
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "clients")
public class ClientInfo {
private String id;
private String name;
private List<String> groups;
public String getId(){ return id;}
public String getName(){ return name;}
public List<String> getGroups(){ return groups;}
}
Check following for example http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html
Inject this class in another class :
#Autowired
private ClientInfo clientInfo;
The above auto wiring will not work if the class is instantiated using "new operator".

Actually I found the reason why it was not working.
All that was needed is to have another class which contains a list of ClientInfo and have #EnableConfigurationProperties and #ConfigurationProperties annotations on it. This is because "clients" in my configuration is a list. After this change we can use #Autowired annotation to inject the configuration.

Related

Read multiple properties file in one go using Spring Boot?

I went through the link: How to pass a Map<String, String> with application.properties and other related links multiple times, but still its not working.
I'm using Spring Boot and Spring REST example. Link Question: How to by default execute the latest version of endpoint in Spring Boot REST?.
I've created mapping something like this and simply read the mapping
get.customers={GET: '/app-data/customers', VERSION: 'v1'}
post.customers={POST: '/app-data/customers', VERSION: 'v1'}
get.customers.custId={GET: '/app-data/customers/{custId}', VERSION: 'v2'}
Code:
private String resolveLastVersion() {
// read from configuration or something
return "2";
}
Code:
#Component
#ConfigurationProperties
#PropertySource("classpath:restendpoint.properties")
public class PriorityProcessor {
private final Map<String, String> priorityMap = new HashMap<>();
public Map<String, String> getPriority() {
return priorityMap;
}
}
Code:
I suggest the following implementation:
#ConfigurationProperties(prefix="request")
public class ConfigurationProps {
private List<Mapping> mapping;
public List<Mapping> getMapping() {
return mapping;
}
public void setMapping(List<Mapping> mapping) {
this.mapping = mapping;
}
}
Class Mapping will denote the information about the single mapping:
public class Mapping {
private String method;
private String url;
private String version;
public Mapping(String method, String url, String version) {
this.method = method;
this.url = url;
this.version = version;
}
public Mapping() {
}
// getters setters here
}
On the Configuration or spring boot application class (the one with main method):
#EnableConfigurationProperties(ConfigurationProps.class)
In the properties file put:
request.mapping[0].method=get
request.mapping[0].url=/customers
request.mapping[0].version=1
request.mapping[1].method=post
request.mapping[1].url=/students
request.mapping[1].version=2
In Filter (I assume you followed my suggestion from the linked question):
#Component
#Order(1)
public class LatestVersionFilter implements Filter {
private List<Mapping> mappings;
public LatestVersionFilter(ConfigurationProps props) {
this.mappings = props.getMapping();
}
}

Best Approach to load application.yml in spring boot application

I am having Spring Boot application and having application.yml with different properties and loading as below.
#Configuration
#ConfigurationProperties(prefix="applicationprops")
public class ApplicationPropHolder {
private Map<String,String> mapProperty;
private List<String> myListProperty;
//Getters & Setters
}
My Service or Controller Class in which I get this properties like below.
#Service
public ApplicationServiceImpl {
#Autowired
private ApplicationPropHolder applicationPropHolder;
public String getExtServiceInfo(){
Map<String,String> mapProperty = applicationPropHolder.getMapProperty();
String userName = mapProperty.get("user.name");
List<String> listProp = applicationPropHolder.getMyListProperty();
}
}
My application.yml
spring:
profile: dev
applicationprops:
mapProperty:
user.name: devUser
myListProperty:
- DevTestData
---
spring:
profile: stagging
applicationprops:
mapProperty:
user.name: stageUser
myListProperty:
- StageTestData
My questions are
In my Service class i am defining a variable and assigning Propertymap for every method invocation.Is it right appoach?
Is there any other better way I can get these maps without assigning local variable.
There are three easy ways you can assign the values to instance variables in your bean class.
Use the #Value annotation as follows
#Value("${applicationprops.mapProperty.user\.name}")
private String userName;
Use the #PostConstruct annotation as follows
#PostConstruct
public void fetchPropertiesAndAssignToInstanceVariables() {
Map<String, String> mapProperties = applicationPropHolder.getMapProperty();
this.userName = mapProperties.get( "user.name" );
}
Use #Autowired on a setter as follows
#Autowired
public void setApplicationPropHolder(ApplicationPropHolder propHolder) {
this.userName = propHolder.getMapProperty().get( "user.name" );
}
There may be others, but I'd say these are the most common ways.
Hope, you are code is fine.
Just use the below
#Configuration
#ConfigurationProperties(prefix="applicationprops")
public class ApplicationPropHolder {
private Map<String,String> mapProperty;
private List<String> myListProperty;
public String getUserName(){
return mapProperty.get("user.name");
}
public String getUserName(final String key){
return mapProperty.get(key);
}
}
#Service
public ApplicationServiceImpl {
#Autowired
private ApplicationPropHolder applicationPropHolder;
public String getExtServiceInfo(){
final String userName = applicationPropHolder.getUserName();
final List<String> listProp = applicationPropHolder.getMyListProperty();
}
}

How can I load propeties in a Map with SpringBoot?

I try to initialize a Map in my SpringBoot application but I am doing something wrong.
My config.properties:
myFieldMap.10000.fieldName=MyFieldName
myFieldMap.10000.fieldName2=MyFieldName2
myFieldMap.10001.fieldName=MyFieldName
myFieldMap.10001.fieldName2=MyFieldName2
myFieldMap.10002.fieldName=MyFieldName
myFieldMap.10003.fieldName2=MyFieldName2
...
(Isn't it possible to use some kind of bracket notation like myFieldMap[10001].fieldName for maps (I saw it used for lists).
I tried with my MyConfig.class:
#PropertySource("classpath:config.properties")
#Component
public class MyConfig {
private java.util.Map<Integer, MyMapping> theMappingsMap = new HashMap<Integer, MyMapping>();
public Map<String, MyMapping> getTheMappingsMap() {
return theMappingsMap;
}
public void setTheMappingsMap(Map<String, MyMapping> theMappingsMap) {
this.theMappingsMap= theMappingsMap;
}
public class MyMapping {
private String fieldName;
private String fieldName2;
public String getFieldName() {
return fieldName;
}
public String getFieldName2() {
return fieldName2;
}
public void setFieldName(final String fieldName) {
this.fieldName = fieldName;
}
public void setFieldName2(final String fieldName) {
this.fieldName2 = fieldName;
}
}
}
How do I have to adapt my code to let SpringBoot initialize my configuration (Map) with the definitions in the config.properties file?
You are missing #ConfigurationProperties annotation. Try this
#PropertySource("classpath:config.properties")
#Configuration
#ConfigurationProperties
public class MyConfig {
private java.util.Map<String, MyMapping> myFieldMap = new HashMap<>();
....
}
Another issue with your code is, if you want to make MyMapping class as an inner class of MyConfig, then you need to declare it as static. Or else you can make it as a separate class.

Spring Data Rest Repository with abstract class / inheritance

I can't get Spring Data Rest with class inheritance working.
I'd like to have a single JSON Endpoint which handles all my concrete classes.
Repo:
public interface AbstractFooRepo extends KeyValueRepository<AbstractFoo, String> {}
Abstract class:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = MyFoo.class, name = "MY_FOO")
})
public abstract class AbstractFoo {
#Id public String id;
public String type;
}
Concrete class:
public class MyFoo extends AbstractFoo { }
Now when calling POST /abstractFoos with {"type":"MY_FOO"}, it tells me: java.lang.IllegalArgumentException: PersistentEntity must not be null!.
This seems to happen, because Spring doesn't know about MyFoo.
Is there some way to tell Spring Data REST about MyFoo without creating a Repository and a REST Endpoint for it?
(I'm using Spring Boot 1.5.1 and Spring Data REST 2.6.0)
EDIT:
Application.java:
#SpringBootApplication
#EnableMapRepositories
public class Application {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
I'm using Spring Boot 1.5.1 and Spring Data Release Ingalls.
KeyValueRepository doesn't work with inheritance. It uses the class name of every saved object to find the corresponding key-value-store. E.g. save(new Foo()) will place the saved object within the Foo collection. And abstractFoosRepo.findAll() will look within the AbstractFoo collection and won't find any Foo object.
Here's the working code using MongoRepository:
Application.java
Default Spring Boot Application Starter.
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
AbstractFoo.java
I've tested include = JsonTypeInfo.As.EXISTING_PROPERTY and include = JsonTypeInfo.As.PROPERTY. Both seem to work fine!
It's even possible to register the Jackson SubTypes with a custom JacksonModule.
IMPORTANT: #RestResource(path="abstractFoos") is highly recommended. Else the _links.self links will point to /foos and /bars instead of /abstractFoos.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Foo.class, name = "MY_FOO"),
#JsonSubTypes.Type(value = Bar.class, name = "MY_Bar")
})
#Document(collection="foo_collection")
#RestResource(path="abstractFoos")
public abstract class AbstractFoo {
#Id public String id;
public abstract String getType();
}
AbstractFooRepo.java
Nothing special here
public interface AbstractFooRepo extends MongoRepository<AbstractFoo, String> { }
Foo.java & Bar.java
#Persistent
public class Foo extends AbstractFoo {
#Override
public String getType() {
return "MY_FOO";
}
}
#Persistent
public class Bar extends AbstractFoo {
#Override
public String getType() {
return "MY_BAR";
}
}
FooRelProvider.java
Without this part, the output of the objects would be separated in two arrays under _embedded.foos and _embedded.bars.
The supports method ensures that for all classes which extend AbstractFoo, the objects will be placed within _embedded.abstractFoos.
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class FooRelProvider extends EvoInflectorRelProvider {
#Override
public String getCollectionResourceRelFor(final Class<?> type) {
return super.getCollectionResourceRelFor(AbstractFoo.class);
}
#Override
public String getItemResourceRelFor(final Class<?> type) {
return super.getItemResourceRelFor(AbstractFoo.class);
}
#Override
public boolean supports(final Class<?> delimiter) {
return AbstractFoo.class.isAssignableFrom(delimiter);
}
}
EDIT
Added #Persistent to Foo.java and Bar.java. (Adding it to AbstractFoo.java doesn't work). Without this annotation I got NullPointerExceptions when trying to use JSR 303 Validation Annotations within inherited classes.
Example code to reproduce the error:
public class A {
#Id public String id;
#Valid public B b;
// #JsonTypeInfo + #JsonSubTypes
public static abstract class B {
#NotNull public String s;
}
// #Persistent <- Needed!
public static class B1 extends B { }
}
Please see the discussion in this resolved jira task for details of what is currently supported in spring-data-rest regarding JsonTypeInfo. And this jira task on what is still missing.
To summarize - only #JsonTypeInfo with include=JsonTypeInfo.As.EXISTING_PROPERTY is working for serialization and deserialization currently.
Also, you need spring-data-rest 2.5.3 (Hopper SR3) or later to get this limited support.
Please see my sample application - https://github.com/mduesterhoeft/spring-data-rest-entity-inheritance/tree/fixed-hopper-sr3-snapshot
With include=JsonTypeInfo.As.EXISTING_PROPERTY the type information is extracted from a regular property. An example helps getting the point of this way of adding type information:
The abstract class:
#Entity #Inheritance(strategy= SINGLE_TABLE)
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME,
include=JsonTypeInfo.As.EXISTING_PROPERTY,
property="type")
#JsonSubTypes({
#Type(name="DECIMAL", value=DecimalValue.class),
#Type(name="STRING", value=StringValue.class)})
public abstract class Value {
#Id #GeneratedValue(strategy = IDENTITY)
#Getter
private Long id;
public abstract String getType();
}
And the subclass:
#Entity #DiscriminatorValue("D")
#Getter #Setter
public class DecimalValue extends Value {
#Column(name = "DECIMAL_VALUE")
private BigDecimal value;
public String getType() {
return "DECIMAL";
}
}

#ConfigurationProperties referencing properties that themselves reference other properties

project.name=my-project
base.url=http://localhost:8080
cas.url=http://my-server:8010/cas
cas.callback.url=${base.url}/${project.name}
Basically I want to use the above in a spring-boot ConfigurationProperties but the casCallbackUrl is always null.
#Component
#ConfigurationProperties(prefix = "cas")
#Getter
#Setter
public class CasSettings {
#NotBlank
private String url; //this is resolved correctly
#NotBlank
private String callbackUrl; //callbackUrl is null
}
update
Well I got it working by camelCasing the property names, but according to the documentation you should be able to use dot notation for property names.
from:
cas.callback.url=${base.url}/${project.name}
to:
cas.callbackUrl=${base.url}/${project.name}
Why is spring-boot not picking up the dot notation?
The dot represents a separate object within the configuration properties object. cas.callback-url would work.
Spring relaxed property is not relaxed enugh to to transform dot notated properties to camel case fields. But you can implement it yourself easily:
#Service
#PropertySource("classpath:git.properties")
public class MngmntService implements EnvironmentAware {
private BuildStatus buildStatus;
private static final Logger LOG = LoggerFactory.getLogger(MngmntService.class);
#Override
public void setEnvironment(Environment env) {
RelaxedPropertyResolver pr = new RelaxedPropertyResolver(env, "git.");
buildStatus = new BuildStatus();
for (Field field : BuildStatus.class.getDeclaredFields()) {
String dotNotation = StringUtils.join(
StringUtils.splitByCharacterTypeCamelCase(field.getName()),
'.'
);
field.setAccessible(true);
try {
field.set(buildStatus, pr.getProperty(dotNotation, field.getType()));
} catch (IllegalArgumentException | IllegalAccessException ex) {
LOG.error("Error setting build property.", ex);
}
}
}
public BuildStatus getBuildStatus() {
return buildStatus;
}
Property object:
public class BuildStatus implements Serializable {
private String tags;
private String branch;
private String dirty;
private String commitId;
private String commitIdAbbrev;
private String commitTime;
private String closestTagName;
private String buildTime;
private String buildHost;
private String buildVersion;
...
}

Resources