Assert that map contains not null vales by given keys - java

I have a code that consumes map of properties with string keys which represents some kind of context. And I want this code to fail if the map does not contain some of the required properties.
The corresponding code might look like this:
SomeResultType businessMethod(Map<String, String> context) {
Assert.isTrue(context.containsKey("A"), "A property must not be empty");
Assert.isTrue(context.containsKey("B"), "B property must not be empty");
Assert.isTrue(context.containsKey("C"), "C property must not be empty");
// ...
}
I wrote a simple replacement by myself with signature like this
public static <K, V> void mapContainsKeys(Map<K, V> map, K... keys)
But I think that something like this must be already implemented somewhere. So I'm searching for library replacement of this code.
It would be great if Spring guys implemented something like this in org.springframework.util.Assert.

if you want to just check that a map contains a list of keys, use this:
map.keySet().containsAll(keys)
if you want more details, to know which ones were missing:
Set<String> missingKeys = new HashSet<>(keys);
missingKeys.removeAll(map.keySet());
and then:
if (!missingKeys.isEmpty()) {
throw new IllegalArgumentException(missingKeys + " keys missing");
}

The fact that you had to write a helper method means that you are probably manipulating maps all over your code.
Looks like a code smell to me, you should probably map your properties to an object that you can validate once and pass everywhere.
Since you are using Spring, and if you are using Spring Boot (most people do nowadays), you can use #ConfigurationProperties to map your configuration properties to an object.
This way you can also add validation like #javax.validation.constraints.NotNull and make sure your properties are valid.

Related

AssertJ JSON property check

I have JSONObject instance which contains some property,
{
"name":"testName",
"age":"23"
}
i use the following assert, but it fails. Is this correct approach to test JSON in assertj.
assertThat(jsonObject).hasFieldOrProperty("name");
If you want to do any serious assertions on JSON object, I would recommend JsonUnit https://github.com/lukas-krecan/JsonUnit
I think it has to do with the fact the JSONObject is like a map which has key-value pairs, while AssertJ expects Java bean-style objects to check if a property exists. I understood this from the document at https://joel-costigliola.github.io/assertj/core/api/org/assertj/core/api/AbstractObjectAssert.html#hasFieldOrProperty(java.lang.String). Hope I am looking at the right place.
I mean to say that a map or JSONObject doesn't have fields declared in it for AssertJ to look for.
You may use JSONObject.has( String key ) instead, I think.
If you use SpringBoot you can use the custom impl. for Assertj
private final BasicJsonTester json = new BasicJsonTester(getClass());
#Test
void testIfHasPropertyName() {
final JSONObject jsonObject = new JSONObject("{\n" +
"\"name\":\"testName\",\n" +
"\"age\":\"23\"\n" +
"}");
assertThat(json.from(jsonObject.toString())).hasJsonPath( "$.name");
}
Fist, you need to traverse the keysets (nodes) using the map class, then verify if the keyset contains the particular node you are looking for.
Map<String, Object> read = JsonPath.read(JSONObject, "$");
assertThat(read.keySet()).contains("name");

Creating a map in config files

We have one application where we use configuration files and they have fields as arrays and normal variables:
metadata {
array=["val1", "val2"]
singleValue=2.0
}
Now, I know how to extract these above values like
config.getStringList("metadata.array").asScala.toArray
and config.getString("metadata.singleValue)
But, is there any way I can define maps here so that I can find value wrt desired key from that map.
This config is an object of
public interface Config extends com.typesafe.config.ConfigMergeable
You can use config.getConfig("metadata") to obtain a (sub)config object.
Converting the (sub)config to a map is something you'll have to do yourself. I would use config.entrySet() to obtain the entries as key-values, and load it into a map that way.
I haven't tried compiling/testing this code, but something like this should work:
Map<String,Object> metadata = new HashMap<>();
for (Map.Entry<String,ConfigValue> entry : config.entrySet()) {
metadata.put(entry.getKey(), entry.getValue().unwrapped());
}

How to customize ModelMapper

I want to use ModelMapper to convert entity to DTO and back. Mostly it works, but how do I customize it. It has has so many options that it's hard to figure out where to start. What's best practice?
I'll answer it myself below, but if another answer is better I'll accept it.
First here are some links
modelmapper getting started
api doc
blog post
random code examples
My impression of mm is that it is very well engineered. The code is solid and a pleasure to read. However, the documentation is very terse, with very few examples. Also the api is confusing because there seems to be 10 ways to do anything, and no indication of why you’d do it one way or another.
There are two alternatives: Dozer is the most popular, and Orika gets good reviews for ease of use.
Assuming you still want to use mm, here’s what I’ve learned about it.
The main class, ModelMapper, should be a singleton in your app. For me, that meant a #Bean using Spring. It works out of the box for simple cases. For example, suppose you have two classes:
class DogData
{
private String name;
private int mass;
}
class DogInfo
{
private String name;
private boolean large;
}
with appropriate getters/setters. You can do this:
ModelMapper mm = new ModelMapper();
DogData dd = new DogData();
dd.setName("fido");
dd.setMass(70);
DogInfo di = mm.map(dd, DogInfo.class);
and the "name" will be copied from dd to di.
There are many ways to customize mm, but first you need to understand how it works.
The mm object contains a TypeMap for each ordered pair of types, such as <DogInfo, DogData> and <DogData, DogInfo> would be two TypeMaps.
Each TypeMap contains a PropertyMap with a list of mappings. So in the example the mm will automatically create a TypeMap<DogData, DogInfo> that contains a PropertyMap that has a single mapping.
We can write this
TypeMap<DogData, DogInfo> tm = mm.getTypeMap(DogData.class, DogInfo.class);
List<Mapping> list = tm.getMappings();
for (Mapping m : list)
{
System.out.println(m);
}
and it will output
PropertyMapping[DogData.name -> DogInfo.name]
When you call mm.map() this is what it does,
see if the TypeMap exists yet, if not create the TypeMap for the <S,
D> source/destination types
call the TypeMap Condition, if it returns FALSE, do nothing and STOP
call the TypeMap Provider to construct a new destination object if necessary
call the TypeMap PreConverter if it has one
do one of the following:
if the TypeMap has a custom Converter, call it
or, generate a PropertyMap (based on Configuration flags plus any custom mappings that were added), and use it
(Note: the TypeMap also has optional custom Pre/PostPropertyConverters that I think will run at this point before and after each mapping.)
call the TypeMap PostConverter if it has one
Caveat: This flowchart is sort of documented but I had to guess a lot, so it might not be all correct!
You can customize every single step of this process. But the two most common are
step 5a. – write custom TypeMap Converter, or
step 5b. – write custom Property Mapping.
Here is a sample of a custom TypeMap Converter:
Converter<DogData, DogInfo> myConverter = new Converter<DogData, DogInfo>()
{
public DogInfo convert(MappingContext<DogData, DogInfo> context)
{
DogData s = context.getSource();
DogInfo d = context.getDestination();
d.setName(s.getName());
d.setLarge(s.getMass() > 25);
return d;
}
};
mm.addConverter(myConverter);
Note the converter is one-way. You have to write another if you want to customize DogInfo to DogData.
Here is a sample of a custom PropertyMap:
Converter<Integer, Boolean> convertMassToLarge = new Converter<Integer, Boolean>()
{
public Boolean convert(MappingContext<Integer, Boolean> context)
{
// If the dog weighs more than 25, then it must be large
return context.getSource() > 25;
}
};
PropertyMap<DogData, DogInfo> mymap = new PropertyMap<DogData, DogInfo>()
{
protected void configure()
{
// Note: this is not normal code. It is "EDSL" so don't get confused
map(source.getName()).setName(null);
using(convertMassToLarge).map(source.getMass()).setLarge(false);
}
};
mm.addMappings(mymap);
The pm.configure function is really funky. It’s not actual code. It is dummy EDSL code that gets interpreted somehow. For instance the parameter to the setter is not relevant, it is just a placeholder. You can do lots of stuff in here, such as
when(condition).map(getter).setter
when(condition).skip().setter – safely ignore field.
using(converter).map(getter).setter – custom field converter
with(provider).map(getter).setter – custom field constructor
Note the custom mappings are added to the default mappings, so you do not need, for example, to specify
map(source.getName()).setName(null);
in your custom PropertyMap.configure().
In this example, I had to write a Converter to map Integer to Boolean. In most cases this will not be necessary because mm will automatically convert Integer to String, etc.
I'm told you can also create mappings using Java 8 lambda expressions. I tried, but I could not figure it out.
Final Recommendations and Best Practice
By default mm uses MatchingStrategies.STANDARD which is dangerous. It can easily choose the wrong mapping and cause strange, hard to find bugs. And what if next year someone else adds a new column to the database? So don't do it. Make sure you use STRICT mode:
mm.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
Always write unit tests and ensure that all mappings are validated.
DogInfo di = mm.map(dd, DogInfo.class);
mm.validate(); // make sure nothing in the destination is accidentally skipped
Fix any validation failures with mm.addMappings() as shown above.
Put all your mappings in a central place, where the mm singleton is created.
I faced a problem while mapping with ModelMapper. Not only properties but also My source and destination type were different. I solved this problem by doing this ->
if the source and destination type are different. For example,
#Entity
class Student {
private Long id;
#OneToOne
#JoinColumn(name = "laptop_id")
private Laptop laptop;
}
And Dto ->
class StudentDto {
private Long id;
private LaptopDto laptopDto;
}
Here, the source and destination types are different. So, if your MatchingStrategies are STRICT, you won't able to map between these two different types.
Now to solve this, Just simply put this below code in the constructor of your controller class or any class where you want to use ModelMapper->
private ModelMapper modelMapper;
public StudentController(ModelMapper modelMapper) {
this.modelMapper = modelMapper;
this.modelMapper.typeMap(Student.class, StudentDto.class).addMapping(Student::getLaptop, StudentDto::setLaptopDto);
}
That's it. Now you can use ModelMapper.map(source, destination) easily. It will map automatically
modelMapper.map(student, studentDto);
I've been using it from last 6 months, I'm going to explain some of my thoughts about that:
First of all, it is recommended to use it as an unique instance (singleton, spring bean,...), that's explained in the manual, and I think all agree with that.
ModelMapper is a great mapping library and wide flexible. Due to its flexibility, there are many ways to get the same result, and that's why it should be in the manual of best practices of when to use one or other way to do the same thing.
Starting with ModelMapper is a little bit difficult, it has a very tight learning curve and sometimes it is not easy to understand the best ways to do something, or how to do some other thing. So, to start it is required to read and understand the manual precisely.
You can configure your mapping as you want using the next settings:
Access level
Field matching
Naming convention
Name transformer
Name tokenizer
Matching strategy
The default configuration is simply the best (http://modelmapper.org/user-manual/configuration/), but if you want to customise it you are able to do it.
Just one thing related to the Matching Strategy configuration, I think this is the most important configuration and is need to be careful with it. I would use the Strict or Standard but never the Loose, why?
Due Loose is the most flexible and intelligent mapper it could be map some properties you can not expect. So, definitively, be careful with it. I think is better to create your own PropertyMap and use Converters if it is needed instead of configuring it as Loose.
Otherwise, it is important to validate all property matches, you verify all it works, and with ModelMapper it's more need due with intelligent mapping it is done via reflection so you will not have the compiler help, it will continue compiling but the mapping will fail without realising it. That's one of the things I least like, but it needs to avoid boilerplate and manual mapping.
Finally, if you are sure to use ModelMapper in your project you should use it using the way it proposes, don't mix it with manual mappings (for example), just use ModelMapper, if you don't know how to do something be sure is possible (investigate,...). Sometimes is hard to do it with model mapper (I also don't like it) as doing by hand but is the price you should pay to avoid boilerplate mappings in other POJOs.
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class EntityDtoConversionUtil {
#Autowired
private ModelMapper modelMapper;
public Object convert(Object object,Class<?> type) {
Object MapperObject=modelMapper.map(object, type);
return MapperObject;
}
}
Here is how you can make a customize Conversion Class and can then autowire it where you would like to convert the object to dto and vice versa.
#Component
public class ConversionUtil {
#Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
public <T,D> D mapItem(T item,Class<D> cl){
return modelMapper().map(item,cl);
}
public <T,D> List<D> map(List<T> list, Class<D> cl){
return list.stream()
.map(item -> modelMapper().map(item, cl))
.collect(Collectors.toList());
}
}

Spring bean initialize maps using external property file

I have a class which has a Map as its member variable. Something like this -
public Clas Engine{
private Map<String,List<String>> filesByKey;
public void setFilesByKey(Map<String,List<String>> map) {
this.filesByKey = map;
}
public Map<String,List<String>> getFilesByKey() {
return filesByKey;
}
}
User can specify any number of keys in the map and its not predefined concept. They can basically group any number of files into one key and provider the map Value.
I was using PropertyOverrideConfigurer and in the properties file, I was trying to do something like this -
engine.filesByKey[key1][0]=file1
engine.filesByKey[key1][1]=file2
engine.filesByKey[key2][0]=anotherfile1
engine.filesByKey[key2][1]=anotherfile2
Now this is not working because the the List value corresponding to key1 or key2 is null to being with. So Spring Bean creation fails with the message that it can not set value to a property which is NULL.
What is the best way to handle this situation?
You should be able to use the LazyMap & LazyList from commons collections to achieve this.
Try to initialize your filesByKey variable with DefaultedMap from commons collections. It can return default value instead of null if map doesn't contain required key.

how to automatically copy values from java bean to protobuf message object using java reflection?

Typically I could copy values between two java beans, which have identical property names, using BeanUtils with java reflection e.g. PropertyUtils.setProperty(....)
In protobuf Message, we use the message builder class to set the value. This works but I would rather use reflection to automatically copy properties from the bean to the message as both have identical property names and type.
When I invoke the PropertyUtils.setProperty on the builder object ( got from message.newBuilder()), I get this message.
java.lang.NoSuchMethodException: Property 'testProp' has no setter method in class 'class teststuff.TestBeanProtos$TestBeanMessage$Builder'
How do I automatically copy values from java bean to protobuf message object (and vice-versa) using java reflection?
I hate to answer my question but I cant believe that I am the only one who ran into this problem. Documenting solution here in case other people are also getting started with protobuf and java. Using reflection saves wrting dozens of getter and setters.
Ok , I managed to get it to work using some of example test code shipping with protobuf. This is a very simple use case; typically a message would be a lot more complex. This code does not handle nested messages or repeated messages.
public static void setMessageBuilder(com.google.protobuf.GeneratedMessage.Builder message,Descriptors.Descriptor descriptor,Object srcObject) throws Exception {
String cname = srcObject.getClass().getName();
/*BeanMapper.getSimpleProperties -- this is a warpper method that gets the list of property names*/
List<String> simpleProps = BeanMapper.getSimpleProperties(srcObject.getClass());
Map map = new HashMap();
for (String pName : simpleProps) {
System.out.println(" processing property "+ pName);
Object value= PropertyUtils.getProperty(srcObject, pName);
if(value==null) continue;
Descriptors.FieldDescriptor fd=descriptor.findFieldByName(pName) ;
System.out.println(" property "+ pName+" , found fd :"+ (fd==null ? "nul":"ok"));
message.setField(fd, value);
System.out.println(" property "+ pName+" set ok,");
}
return ;
}
I may be off, but would protostuff help? It has nice extended support for working with other data formats, types. And even if it didn't have direct conversion support, if you go to/from JSON there are many choices for good data binding.
You can go throw all properties getClass().getFields() and make copy using reflection. It will be smt like:
for(Field f : to.getClass().getFields()){
f.set(to, from.getClass().getField(f.getName()).get(from));
}
+ probably you might be use field.setAccessible(true) invocation.
I don't know the size of your project but you may want to try Dozer, a mapper that recursively copies data from one object to another of the same type or between different complex types. Supports implicit and explicit mapping as well. I used it in a big project and worked very well. It could be as simple as
Mapper mapper = new DozerBeanMapper();
DestinationObject destObject = mapper.map(sourceObject, DestinationObject.class);
I've got the same issue, the solution is a little bit tricky.
Please use
MethodUtils.invokeMethod
instead.
where the method name is "setXXX".

Categories