With AssertJ I can check that Map has key with referencing value satisfying specific Consumer:
#AllArgsConstructor
#Getter
public static class User {
private Long id;
private String name;
}
#Test
public void x() {
Map<String, User> map = new HashMap<>();
map.put("key", new User(123, "Random Hacker"));
Assertions.assertThat(map).hasEntrySatisfying("key", __ -> {
Assertions.assertThat(__.getName()).isEqualTo("Random Hacker");
});
}
Is it possible to check conditions for specific Map value for given key (along with key/value presence) with Hamcrest?
NOTE public SELF hasEntrySatisfying(K key, Consumer<? super V> valueRequirements) has been added to AspectJ as of v3.6.0 (2016-11-21).
According to Using Hamcrest for testing - Tutorial there is a hasEntry method available in hamcrest.
Its usage is as such:
org.hamcrest.MatcherAssert.assertThat(myMap, org.hamcrest.Matchers.hasEntry("bar", "foo"))
If you need a more general matcher there is this method:
Example:
assertThat(myMap, hasEntry(equalTo("bar"), equalTo("foo")))
Any of the many matchers from Hamcrest or your own custom matcher can be used instead of "equalTo".
Related
i have a custom case that some of my dto's have a field of type X, and i need to map this class to Y by using a spring service method call(i do a transactional db operation and return an instance of Y). But in this scenario i need to use existing value of Y field. Let me explain it by example.
// DTO
public class AnnualLeaveRequest {
private FileInfoDTO annualLeaveFile;
}
//ENTITY
public class AnnualLeave {
#OneToOne
private FileReference annualLeaveFile;
}
#Mapper
public abstract class FileMapper {
#Autowired
private FileReferenceService fileReferenceService;
public FileReference toFileReference(#MappingTarget FileReference fileReference, FileInfoDTO fileInfoDTO) {
return fileReferenceService.updateFile(fileInfoDTO, fileReference);
}
}
//ACTUAL ENTITY MAPPER
#Mapper(uses = {FileMapper.class})
public interface AnnualLeaveMapper {
void updateEntity(#MappingTarget AnnualLeave entity, AnnualLeaveRequest dto);
}
// WHAT IM TRYING TO ACHIEVE
#Component
public class MazeretIzinMapperImpl implements tr.gov.hmb.ikys.personel.izinbilgisi.mazeretizin.mapper.MazeretIzinMapper {
#Autowired
private FileMapper fileMapper;
#Override
public void updateEntity(AnnualLeave entity, AnnualLeaveUpdateRequest dto) {
entity.setAnnualLeaveFile(fileMapper.toFileReference(dto.getAnnualLeaveFile(), entity.getAnnualLeaveFile()));
}
}
But mapstruct ignores the result of "FileReference toFileReference(#MappingTarget FileReference fileReference, FileInfoDTO fileInfoDTO) " and does not map the result of it to the actual entity's FileReference field. Do you have any idea for resolving this problem?
Question
How do I replace the annualLeaveFile property while updating the AnnualLeave entity?
Answer
You can use expression to get this result. For example:
#Autowired
FileMapper fileMapper;
#Mapping( target = "annualLeaveFile", expression = "java(fileMapper.toFileReference(entity.getAnnualLeaveFile(), dto.getAnnualLeaveFile()))" )
abstract void updateEntity(#MappingTarget AnnualLeave entity, AnnualLeaveRequest dto);
MapStruct does not support this without expression usage. See the end of the Old analysis for why.
Alternative without expression
Instead of fixing it in the location where FileMapper is used, we fix it inside the FileMapper itself.
#Mapper
public abstract class FileMapper {
#Autowired
private FileReferenceService fileReferenceService;
public void toFileReference(#MappingTarget FileReference fileReference, FileInfoDTO fileInfoDTO) {
FileReference wanted = fileReferenceService.updateFile(fileInfoDTO, fileReference);
updateFileReference(fileReference, wanted);
}
// used to copy the content of the service one to the mapstruct one.
abstract void updateFileReference(#MappingTarget FileReference fileReferenceTarget, FileReference fileReferenceFromService);
}
Old analysis
The following is what I notice:
(Optional) your FileMapper class is not a MapStruct mapper. This can just be a normal class annotated with #Component, since it does not have any unimplemented abstract methods. (Does not affect code generation of the MazeretIzinMapper implementation)
(Optional, since you have this project wide configured) you do not have componentModel="spring" in your #Mapper definition, maybe you have this configured project wide, but that is not mentioned. (required for the #Autowired annotation, and #Component on implementations)
Without changing anything I already get a working result as you want it to be, but for non-update methods (not listed in your question, but was visible on the gitter page where you also requested help) the FileMapper as is will not be used. It requires an additional method that takes only 1 argument: public FileReference toFileReference(FileInfoDTO fileInfoDTO)
(Edit) to get rid of the else statement with null value handling you can add nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE to the #Mapper annotation.
I've run a test and with 1.5.0.Beta2 and 1.4.2.Final I get the following result with the thereafter listed FileMapper and MazeretIzinMapper classes.
Generated mapper implementation
#Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2022-03-11T18:01:30+0100",
comments = "version: 1.4.2.Final, compiler: Eclipse JDT (IDE) 1.4.50.v20210914-1429, environment: Java 17.0.1 (Azul Systems, Inc.)"
)
#Component
public class MazeretIzinMapperImpl implements MazeretIzinMapper {
#Autowired
private FileMapper fileMapper;
#Override
public AnnualLeave toEntity(AnnualLeaveRequest dto) {
if ( dto == null ) {
return null;
}
AnnualLeave annualLeave = new AnnualLeave();
annualLeave.setAnnualLeaveFile( fileMapper.toFileReference( dto.getAnnualLeaveFile() ) );
return annualLeave;
}
#Override
public void updateEntity(AnnualLeave entity, AnnualLeaveRequest dto) {
if ( dto == null ) {
return;
}
if ( dto.getAnnualLeaveFile() != null ) {
if ( entity.getAnnualLeaveFile() == null ) {
entity.setAnnualLeaveFile( new FileReference() );
}
fileMapper.toFileReference( entity.getAnnualLeaveFile(), dto.getAnnualLeaveFile() );
}
}
}
Source classes
Mapper
#Mapper( componentModel = "spring", uses = { FileMapper.class }, nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE )
public interface MazeretIzinMapper {
AnnualLeave toEntity(AnnualLeaveRequest dto);
void updateEntity(#MappingTarget AnnualLeave entity, AnnualLeaveRequest dto);
}
FileMapper component
#Mapper
public abstract class FileMapper {
#Autowired
private FileReferenceService fileReferenceService;
public FileReference toFileReference(#MappingTarget FileReference fileReference, FileInfoDTO fileInfoDTO) {
return fileReferenceService.updateFile( fileInfoDTO, fileReference );
}
public FileReference toFileReference(FileInfoDTO fileInfoDTO) {
return toFileReference( new FileReference(), fileInfoDTO );
}
// other abstract methods for MapStruct mapper generation.
}
Why the exact wanted code will not be generated
When generating the mapping code MapStruct will use the most generic way to do this.
An update mapper has the following criteria:
The #MappingTarget annotated argument will always be updated.
It is allowed to have no return type.
the generic way to update a field is then as follows:
// check if source has the value.
if (source.getProperty() != null) {
// Since it is allowed to have a void method for update mappings the following steps are needed:
// check if the property exists in the target.
if (target.getProperty() == null) {
// if it does not have the value then create it.
target.setProperty( new TypeOfProperty() );
}
// now we know that target has the property so we can call the update method.
propertyUpdateMappingMethod( target.getProperty(), source.getProperty() );
// The arguments will match the order as specified in the other update method. in this case the #MappingTarget annotated argument is the first one.
} else {
// default behavior is to set the target property to null, you can influence this with nullValuePropertyMappingStrategy.
target.setProperty( null );
}
I have simple class and field id is annotated with custom deserializer.
public class TestRequest implements Serializable {
#NotNull
#Pattern(regexp = "^[a-zA-Z0-9=+]*$")
#JsonDeserialize(using = StringDeserializer.class)
#JsonProperty
private String id;
//getter
//setter
}
and the custom deserializer class :
public class StringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {
Iterator<String> iterator = jsonParser.readValuesAs(String.class);
String id = iterator.next();
return id + "0000";
}
}
When i test with with Jersey REST test, the deserilizer is called and works as expected. But with websphere it does not called. Any idea why its not called.
I am using jaxrs 1.1
You may need to add the #Provider annotation to your StringDeserializer class, otherwise the JAX-RS runtime will not recognize the class as a provdier.
Even though your class extends JsonDeserializer<String>, which itself is probably annotated with #Provider, the JAX-RS runtime will not scan libraries for annotations, as doing negatively impacts performance.
I made it working in Websphere by moving the annotation #JsonDeserialize(using = StringDeserializer.class) to setter method of the class
public class TestRequest implements Serializable {
#NotNull
#Pattern(regexp = "^[a-zA-Z0-9=+]*$")
#JsonProperty
private String id;
#JsonDeserialize(using = StringDeserializer.class)
public setId(String id){
this.id=id;
}
//getter
//setter
}
I'm using EclipseLink and I'd like to check whether entity and table definitions are consistent.
I found "Integrity Checker" and tried it.
public final class EMF {
public static class EnableIntegrityChecker implements SessionCustomizer {
#Override
public void customize(Session session) throws Exception {
session.getIntegrityChecker().checkDatabase();
session.getIntegrityChecker().setShouldCatchExceptions(false);
}
}
private static final EntityManagerFactory INSTANCE;
static {
String appId = SystemProperty.applicationId.get();
Map<String, String> overWriteParam = new HashMap<>();
overWriteParam.put(
PersistenceUnitProperties.SESSION_CUSTOMIZER,
EnableIntegrityChecker.class.getName());
INSTANCE = Persistence.createEntityManagerFactory("unit", overWriteParam);
}
private EMF() {
}
public static EntityManager create() {
return INSTANCE.createEntityManager();
}
}
Some cases it can detect inconsistency, but some cases can not.
If entity has variable A and table does not have column A, Integrity Checker can found inconsistency.
If table has colume A and entity does not have variable A, Integrity Checker can not found inconsistency.
If column A in table is int and variable A in entity is String, Integrity Checker can not found inconsistency.
How can I detect inconsistency in case 2 and 3?
You can extend IntegrityChecker, override it's checkTable method and use it via session.setIntegrityChecker(customIntegrityChecker). Note that some of validations are located in ClassDecriptor#checkDatabase so it's hard to directly re-use them and properly report exact error cause.
I want to convert Optional<BigDecimal> in morphia. I created BigDecimalConverter, and it works fine. Now I want to create OptionalConverter.
Optional can hold any object type. In my OptionalConverter.encode method I can extract underlying object, and I'd like to pass it to default mongo conversion. So that if there is string, I'll just get string, if there is one of my entities, I'll get encoded entity. How can I do it?
There are two questions:
1. How to call other converters?
2. How to create a converter for a generic class whose type parameters are not statically known?
The first one is possible by creating the MappingMongoConveter and the custom converter together:
#Configuration
public class CustomConfig extends AbstractMongoConfiguration {
#Override
protected String getDatabaseName() {
// ...
}
#Override
#Bean
public Mongo mongo() throws Exception {
// ...
}
#Override
#Bean
public MappingMongoConverter mappingMongoConverter() throws Exception {
MappingMongoConverter mmc = new MappingMongoConverter(
mongoDbFactory(), mongoMappingContext());
mmc.setCustomConversions(new CustomConversions(CustomConverters
.create(mmc)));
return mmc;
}
}
public class FooConverter implements Converter<Foo, DBObject> {
private MappingMongoConverter mmc;
public FooConverter(MappingMongoConverter mmc) {
this.mmc = mmc;
}
public DBObject convert(Foo foo) {
// ...
}
}
public class CustomConverters {
public static List<?> create(MappingMongoConverter mmc) {
List<?> list = new ArrayList<>();
list.add(new FooConverter(mmc));
return list;
}
}
The second one is much more difficult due to type erasure. I've tried to create a converter for Scala's Map but haven't found a way. Unable to get the exact type information for the source Map when writing, or for the target Map when reading.
For very simple cases, e.g. if you don't need to handle all possible parameter types, and there is no ambiguity while reading, it may be possible though.
Since version 1.4.2 of XStream, the XStreamConverter annotation takes additional parameters (very good feature and just what I need).
#XStreamConverter(value=CustomXStreamConverter.class, strings={xyz"})
private List<String> phones;
But how can I read this values (xyz) in my custom converter?
public class CustomXStreamConverter implements Converter {
//?
}
I figure out the solution, just override the class constructor in order to receive the parameter.
public class CustomXStreamConverter implements Converter {
private String alias;
public ListToStringXStreamConverter(String alias) {
super();
this.alias = alias; //xyz
}
//...