SnakeYaml "Unable to find property error" - java

Here is part of my config.yml:
#Authenctication
AuthenticationConfig:
AuthencticationType: LDAP
LDAPConfig:
LDAPUrl: ldap://localhost:389
ConnectionType: simple
LDAPSecurityConfig:
RootDN: cn=manager,dc=maxcrc,dc=com
RootPassword: secret
UserSearchDN: ou=People,dc=maxcrc,dc=com
GroupdSearchDB: ou=Groups,dc=maxcrc,dc=com
I have a class used for parsing:
public class YamlConfiguraiton {
private AuthenticationConfiguration AuthenticationConfig;
public void setAuthenticationConfig(AuthenticationConfiguration AuthenticationConfig) {
this.AuthenticationConfig = AuthenticationConfig;
}
public AuthenticationConfiguration getAuthenticationConfig() {
return this.AuthenticationConfig;
}
}
However, when I run
try(InputStream in = new FileInputStream(new File(ymalPath))) {
yamlConfig = yaml.loadAs(in, YamlConfiguraiton.class);
} catch (IOException e) {
e.printStackTrace();
}
the following error happens:
Exception in thread "main" Cannot create property=AuthenticationConfig for JavaBean=com.ibm.entity.matching.common.bootstrap.YamlConfiguraiton#e7860081
in 'reader', line 2, column 1:
AuthenticationConfig:
^
Unable to find property 'AuthenticationConfig' on class: com.ibm.entity.matching.common.bootstrap.YamlConfiguraiton
in 'reader', line 3, column 4:
AuthencticationType: LDAP
^
at org.yaml.snakeyaml.constructor.Constructor$ConstructMapping.constructJavaBean2ndStep(Constructor.java:270)
at org.yaml.snakeyaml.constructor.Constructor$ConstructMapping.construct(Constructor.java:149)
at org.yaml.snakeyaml.constructor.Constructor$ConstructYamlObject.construct(Constructor.java:309)
at org.yaml.snakeyaml.constructor.BaseConstructor.constructObjectNoCheck(BaseConstructor.java:204)
at org.yaml.snakeyaml.constructor.BaseConstructor.constructObject(BaseConstructor.java:193)
at org.yaml.snakeyaml.constructor.BaseConstructor.constructDocument(BaseConstructor.java:159)
at org.yaml.snakeyaml.constructor.BaseConstructor.getSingleData(BaseConstructor.java:146)
at org.yaml.snakeyaml.Yaml.loadFromReader(Yaml.java:524)
at org.yaml.snakeyaml.Yaml.loadAs(Yaml.java:518)
at com.ibm.entity.matching.bootstrap.EntityMatching.boot(EntityMatching.java:55)
at com.ibm.entity.matching.bootstrap.EntityMatching.main(EntityMatching.java:35)
Caused by: org.yaml.snakeyaml.error.YAMLException: Unable to find property 'AuthenticationConfig' on class: com.ibm.entity.matching.common.bootstrap.YamlConfiguraiton
at org.yaml.snakeyaml.introspector.PropertyUtils.getProperty(PropertyUtils.java:159)
at org.yaml.snakeyaml.introspector.PropertyUtils.getProperty(PropertyUtils.java:148)
at org.yaml.snakeyaml.constructor.Constructor$ConstructMapping.getProperty(Constructor.java:287)
at org.yaml.snakeyaml.constructor.Constructor$ConstructMapping.constructJavaBean2ndStep(Constructor.java:208)
... 10 more
Why it complains about cannot finding property AuthenticationConfig while AuthenticationConfig is just the name of the instance variable?
UPDATE
After I changed the instance variables from "private" to "public", they were recognized by SnakeYaml, but this is not what we expect for sure. The classes are not recognized as JavaBean.
UPDATE
I found the root cause. It is the naming convention. If you want SnakeYaml to parse your yaml file, camelCase has to be complied with. The name of setter and getter method is also important. Say there is a private instance variable called ldapConfig, then its getter and setter's name has to be getLdapConfig and setLdapConfig, even getLDAPConfig and setLDAPConfig won't work.

The main reason for the error is that you need to define all the attributes present in Yaml file in POJO class (i.e. YamlConfiguraiton).
You can use the below code to skip the undefined properties.
Representer representer = new Representer();
representer.getPropertyUtils().setSkipMissingProperties(true);
Yaml yaml = new Yaml(new Constructor(YamlConfiguraiton.class), representer);
Firstly, rename the attribute names to camelCase in Yaml file.
Refer the below code:-
Code:-
public class YamlReadCustom {
private static String yamlPath = "/authentication.yaml";
public static void main(String[] args) {
Representer representer = new Representer();
representer.getPropertyUtils().setSkipMissingProperties(true);
Yaml yaml = new Yaml(new Constructor(Authentication.class), representer);
try(InputStream in = YamlReadCustom.class.getResourceAsStream (yamlPath)) {
Authentication authentication = yaml.loadAs(in, Authentication.class);
System.out.println(authentication.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
Authentication:-
public class Authentication {
private String authenticationConfig;
private String authencticationType;
private LdapConfig ldapConfig;
//getters and setters
}
LdapConfig:-
public class LdapConfig {
private String ldapUrl;
private String connectionType;
private Map<String, Object> ldapSecurityConfig;
//getters and setters
}
authentication.yaml
authenticationConfig:
authencticationType: LDAP
ldapConfig:
ldapUrl: ldap://localhost:389
connectionType: simple
ldapSecurityConfig:
rootDn: cn=manager,dc=maxcrc,dc=com
rootPassword: secret
userSearchDn: ou=People,dc=maxcrc,dc=com
groupdSearchDb: ou=Groups,dc=maxcrc,dc=com

Related

Junit test class for dependency with value from application.properties

I am writing Junit test case for the following class :
#Component
public class ExpandParam {
/* expand parameter with value "expand" */
#Value("${api.expand.value}")
private String expandParam;
public MultiValueMap<String, String> getExpandQueryParam(String[] expand) {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
// Creating comma separated format string
StringBuilder builder = new StringBuilder();
for (String value : expand) {
if(!expand[expand.length-1].equals(value)) {
builder.append(value+", ");
}
else {
builder.append(value);
}
}
String expandText = builder.toString();
queryParams.add(expandParam, expandText);
return queryParams;
}
}
The test class is following :
public class ExpandParamTest {
#InjectMocks
#Spy
ExpandParam expandQueryParam;
// #Value("${api.expand.value}")
// private String expandParam;
private String[] expand = {"fees"};
#Before
public void setup() {
ReflectionTestUtils.setField(expandQueryParam, "expandParam", "expand");
}
#Test
public void testExpandParam() {
MultiValueMap<String, String> queryParams = expandQueryParam.getExpandQueryParam(expand);
try {
System.out.println(new ObjectMapper().writeValueAsString(queryParams));
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
In application. properties files I have set the values :
#expand param
api.expand.value: expand
I am new to this, can any one tell me where I am making the mistake:
Getting the following error:
java.lang.IllegalArgumentException: Either targetObject or targetClass for the field must be specified
at org.springframework.util.Assert.isTrue(Assert.java:121)
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:178)
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:107)
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:91)
at com.aig.rs.products.orchestrator.api.utils.ExpandParamTest.setup(ExpandParamTest.java:29)
#Value is a spring annotation, it depends on the Spring Context to function. If you want #Value to read the value from your application properties then you need to convert your unit test into a #SpringBootTest. Take a look at this tutorial to understand a bit more about Spring Test.
You're also using ReflectionTestUtils.setField(expandQueryParam, "expandParam", "expand"); which will just set a value to this field, not read it from properties. This exception you're seeing is because expandQueryParam is null, these annotations #Spy and #InjectMocks are Mockito annotations and for them to initialize your object you need to enable mockito annotations, you can do this by adding #ExtendWith(MockitoExtension.class) on top of your class or using MockitoAnnotations.initMocks(this) in setUp method.
I don't think you need mockito to test this class, in my opinion going for a Spring Test would be a better option this way you can also test the reading of the property key.

Spring Boot - Assign value to Class Member from YML file - Null Pointer Exception

I am using below annotations in my config class to get the values from properties file(yml).
Configuration
EnableConfigurationProperties
ConfigurationProperties (prefix = "notification")
I am able to get the values inside public methods without problem using the class . But I am getting 'Error Creating bean' Error when I try to assign value instance variable of the class using config class.
Below is my code. Can someone please throw some light.
This is my config class
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties (prefix = "notification")
public class NotifyYaml {
private String subscriptionId;
public String getSubscriptionId() {
return subscriptionId;
}
public void setSubscriptionId(String subscriptionId) {
this.subscriptionId = subscriptionId;
}
Below is the class where I am getting error during startup.
#Component
public class PubSubController {
#Autowired
private NotifyYaml notify;
public PubSubController() {
// TODO Auto-generated constructor stub
}
String projectId = "ccc-g-pre-proj-cacdate";
//Error in this line
String subscriptionId = notify.getSubscriptionId();
The #Autowired object only gets filled in after the object is created.
This means that while the object is being created, it tries to call a method from a null object.
I would suggest using something like a #PostConstruct method. (Note: you will need to include javax.annotations into your project somehow.)
String subscriptions; // remove the value for now...
#PostConstruct
private void init() {
subscriptions = notify.getSubscriptionId(); // ...and add it back in here.
}

Deserialize property file that contains dot with Jackson

In our application we are trying to read a flat property file using Jackson to match the properties to our POJO
Everything works fine, but when the property name contains some dot, the wholo POJO is set to null
Here is a sample of the property file
p.test=Just a test
Here is my POJO
public class BasicPOJO {
#JsonProperty("p.test")
private String test;
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
}
And here is how I map it
InputStream in = ApplicationProperties.class.getClassLoader()
.getResourceAsStream("application.properties");
JavaPropsMapper mapper = new JavaPropsMapper();
try {
BasicPOJO myProperties = mapper.readValue(in,
BasicPOJO .class);
LOGGER.debug("Loaded properties {}", myProperties); //myProperties.test is null here
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Any help will be appreciated
Dot in property names is used to represent nested objects.
This is described here
https://github.com/FasterXML/jackson-dataformats-text/blob/master/properties/README.md#basics-of-conversion
Since default java.util.Properties can read "flat" key/value entries in, what is the big deal here?
Most properties files actually use an implied structure by using a naming convention; most commonly by using period ('.') as logical path separator.
You can disable it by using JavaPropsSchema.withoutPathSeparator() as described here https://github.com/FasterXML/jackson-dataformats-text/blob/master/properties/README.md#javapropsschemapathseparator
JavaPropsSchema schema = JavaPropsSchema.emptySchema()
.withoutPathSeparator();
BasicPOJO myProperties = mapper.readerFor(BasicPOJO.class)
.with(schema)
.readValue(source);

Yaml file value is not loading using jackson in java

I want to load a yaml file & store it in Config.java.
Here is my yaml file: (Its much bigger. I am giving a simplified version)
---
application:
admin:
jobInterValTime: 1440
customer: lc
system:
mongo:
host: localhost
port: 27017
dbName: LC_Test
collections:
groupsCollection: groups
membershipCollection: memberships
personsCollection: persons
Here is Config.java:
public class Config {
private Application application;
private System system;
//Getter setter
}
Application.java
public class Application {
private Admin admin;
//Getter Setter
}
Admin.java
public class Admin {
private String jobInterValTime;
private String customer;
//Getter Setter
}
System.java
public class System {
private Mongo mongo;
//Getter Setter
}
Mongo.java
public class Mongo {
private String host;
private String port;
private String dbName;
private Map<String, String> collections;
//Getter Setter
}
But the application & system object inside Config.java is coming null.No exception is happening. Can anybody help?
Here is what I have written.
Config config = null;
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
try{
config = objectMapper.readValue(new File("src/test/java/resources/test1.yaml"), Config.class);
//System.out.println(application.getAdmin().getCustomer());
// System.out.println(unidataConfig.getApplication().getAdmin().getCustomer());
} catch(Exception e) {
e.printStackTrace();
}
I don't know the root cause here, code looks good. But one thing you could try is to read contents as Map or JsonNode first, and see how structure looks like. There may well be a mismatch.
I solved the problem. The mistake was very stupid. In one setter method I have written like this: var1 = var1 instead of this.var1 = var1.

Jackson - Java bean to JSON string : uppercase variable converted into lowercase in JSON

I am converting Java bean to JSON string using writeValueAsString method of ObjectMapper where uppercase variables from Java bean is being changed to lowercase in JSON string.
Jackson 2.7.4 version implemented.
Base bean sample -
public class BaseBean {
private static final long serialVersionUID = 3947489072259877540L;
private int _iXId;
private String _sPNR;
private ArrayList _alMinPriced = new ArrayList<TermBean>();
public int getXId() {
return _iXId;
}
public void setXId(int id) {
_iXId = id;
}
public String getPNRNumber() {
return _sPNR;
}
public void setPNRNumber(String _spnr) {
_sPNR = _spnr;
}
public ArrayList getMinPriced() {
return _alMinPriced;
}
public void setMinPriced(ArrayList minPriced) {
_alMinPriced = minPriced;
}
public void setMinPriced(TermBean bnTerm) {
_alMinPriced.add(bnTerm);
}
}
Earlier, we were using net.sf.json.JSON & JSONSerializer for Java bean to JSON conversion. And generated JSON string was having similar naming as what we are having Java bean. Due to performance issue, I want to change this & implement Jackson.
Restrictions : we can't change Java bean naming convention as these beans are from older project and there is little scope to change the variable names in bean and even adding json properties in each bean.
I have tried below code but that didn't worked
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE);
Also, I have tried customized PropertyNamingStrategy but not clear on this.
Edited :
net.sf.json.JSON generated JSON string as mentioned below for above bean :
{"XId":11,"PNRNumber":"123456789","minPriced":[{"name":"JSON"},{"name":"simple"}]}
Jackson generated JSON string as mentioned below for above bean :
{"xid":11,"pnrnumber":"123456789","minPriced":[{"name":"JSON"},{"name":"Jackson"}]}
As you can see that "XId" converted to "xid" in jackson and "PNRNumber" converted to "pnrnumber" in jackson.
Is there any configuration changes available in Jackson to avoid such modification.
OR How to handle such scenario.
Following jars have been used:
jackson-core-2.7.4.jar
jackson-annotations-2.7.4.jar
jackson-databind-2.7.4.jar
Step 1: Please write following Mixin as follows:
import java.util.ArrayList;
import com.fasterxml.jackson.annotation.JsonProperty;
public abstract class MixIn {
#JsonProperty("PNRNumber")
abstract String getPNRNumber();
#JsonProperty("XId")
abstract int getXId();
#JsonProperty("minPriced")
abstract ArrayList getMinPriced();
}
Step 2: Please write your Module as follows:-
import com.fasterxml.jackson.databind.module.SimpleModule;
public class MyModule extends SimpleModule{
public MyModule() {
super("ModuleName");
}
#Override
public void setupModule(SetupContext context){
context.setMixInAnnotations(BaseBean.class, MixIn.class);
}
}
Step 3: Now its time to get json String as follows:
TermBean bean1=new TermBean("JSON");
TermBean bean2=new TermBean("simple");
ArrayList list=new ArrayList();
list.add(bean1);
list.add(bean2);
BaseBean bb=new BaseBean();
bb.setXId(11);
bb.setPNRNumber("123456789");
bb.setMinPriced(list);
ObjectMapper mapper = new ObjectMapper();
Module myModule = new MyModule();
mapper.registerModule(myModule);
String jsonInString = mapper.writeValueAsString(bb);
System.out.printf( "JSON: %s", jsonInString );
Output:
JSON:
{"XId":11,"PNRNumber":"123456789","minPriced":[{"name":"JSON"},{"name":"simple"}]}
Hope this helps.
Add Json Property with required keycase. Create variable with lowercase.
public class BaseBean {
#JsonProperty("XId")
private int xId;
..
}
Hope this will help

Categories