Boolean defaultValue MapStruct - java

I'm trying to set a defaultValue for a boolean field using MapStruct, but the generated code simply ignores it.
My code:
public class CreateEventRequest {
#NotNull
#JsonProperty
private Boolean isPrivate;
#JsonProperty
private Boolean friendCanInviteFriends;
#JsonProperty
private boolean showGuestList;
public boolean isPrivate() {
return isPrivate;
}
public String getDescription() {
return description;
}
public boolean isFriendCanInviteFriends() {
return friendCanInviteFriends;
}
public boolean isShowGuestList() {
return showGuestList;
}
}
My mapper:
#Mapper(componentModel = "spring")
public interface CreateEventRequestMapper {
#Mapping(target = "showGuestList", source = "showGuestList", defaultExpression = "java(true)")
#Mapping(target = "friendCanInviteFriends", source = "friendCanInviteFriends", defaultValue = "true")
Event map(CreateEventRequest request);
}
The generated code:
public class CreateEventRequestMapperImpl implements CreateEventRequestMapper {
#Override
public Event map(CreateEventRequest request) {
if ( request == null ) {
return null;
}
Event event = new Event();
event.setShowGuestList( request.isShowGuestList() );
event.setFriendCanInviteFriends( request.isFriendCanInviteFriends() );
event.setPrivate( request.isPrivate() );
return event;
}
}
As you can see, I've tried using primitive/non-primitive type but it simply ignores the defaultValue.
Am I missing something here?
Thanks!

The problem is that the return type of your getter methods in the source object is always primitive which can't be null, you need to return Boolean.
MapStruct doesn't support direct private field access which requires reflection.

Related

Enum lookup by positioned values using MapStruct

I have implemented my basic requirements, which work well in one simple scenario as mentioned below code snippet. But for new requirements what is the best way out there I need help.
New requirement: Statuses in numeric format are used on other services but in request-response status representation are these user-friendly string ["Not Started", "In Progress", "Completed"]
#AllArgsConstructor
#Getter
public enum StatusEnum {
NOT_STARTED(1,"Not Started"),
IN_PROGRESS(2, "In Progress"),
COMPLETED(3, "Completed");
private final int key;
private final String value;
}
Below is my MapStruct logic to convert enum to string and visa-versa conversion logic. This works fine for basic requirements. But what is the logic of the new requirement?
ActionItem.java:
private Constants.StatusEnum status;
Basic Requirements works with below implementation:
#AllArgsConstructor
#Getter
public enum StatusEnum {
NOT_STARTED("Not Started"),
IN_PROGRESS("In Progress"),
COMPLETED("Completed");
private final String value;
}
#Mapper
public interface ActionItemMapper extents BaseMapper {
#Mapping(source = "status", target = "status", qualifiedByName = "statusEnumToString")
ActionItemResponse toActionItemResponse(ActionItem actionItem);
}
#Mapper
public interface BaseMapper {
#Named("statusEnumToString")
default String statusEnumToString(Constants.StatusEnum statusEnum) {
return statusEnum.getValue();
}
#Named("statusStringToEnum")
default Constants.StatusEnum statusStringToEnum(String status) {
return List.of(Constants.StatusEnum.values()).stream().filter(s -> s.getValue().equals(status)).findAny()
.orElse(null);
}
}
I got the solution.
#AllArgsConstructor
#Getter
public enum StatusEnum {
NOT_STARTED(1, "Not Started"),
IN_PROGRESS(2, "In Progress"),
COMPLETED(3, "Completed");
private final String key;
private final String value;
}
#Mapper
public interface ActionItemMapper extents BaseMapper {
#Mapping(source = "status", target = "status", qualifiedByName = "statusEnumToString")
ActionItemResponse toActionItemResponse(ActionItem actionItem);
}
#Mapper
public interface BaseMapper {
#Named("statusEnumKeyToValue")
default String statusEnumKeyToValue(Integer status) {
String value = null;
for (Constants.StatusEnum statusEnum: Constants.StatusEnum.values()) {
if (statusEnum.getKey().equals(status)) {
value = statusEnum.getValue();
break;
}
}
if (value == null) {
throw new IllegalArgumentException("No status value found for status key " +status);
}
return value;
}
#Named("statusEnumValueToKey")
default Integer statusEnumValueToKey(String status) {
return statusStringToEnum(status).getKey();
}
default Constants.StatusEnum statusStringToEnum(String status) {
return List.of(Constants.StatusEnum.values()).stream().filter(s -> s.getValue().equals(status)).findAny()
.orElseThrow()
}
}

Storing Enum custom Values with JPA

I have an enum:
public enum NotificationType {
OPEN("open"),
CLOSED("closed");
public String value;
NotificationType(String value) {
this.value = value;
}
}
I want to pass the custom string open or closed rather than OPEN or CLOSED to entity. Currently, I've mapped it in the entity as follows:
#Enumerated(EnumType.STRING)
private NotificationType notificationType;
Which is the best way to store/ fetch enum value?
You can create a custom converter like this:
#Converter(autoApply = true)
public class NotificationTypeConverter implements AttributeConverter<NotificationType, String> {
#Override
public String convertToDatabaseColumn(NotificationType notificationType) {
return notificationType == null
? null
: notificationType.value;
}
#Override
public NotificationType convertToEntityAttribute(String code) {
if (code == null || code.isEmpty()) {
return null;
}
return Arrays.stream(NotificationType.values())
.filter(c -> c.value.equals(code))
.findAny()
.orElseThrow(IllegalArgumentException::new);
}
}
And perhaps you'll need to remove annotation from your notificationType field so that this converter takes effect.
Yes, basically you have to develop a custom converter for that but I suggest you use Optional to avoid dealing with null and exceptions.
Add in NotificationType:
public static Optional<NotificationType> getFromValue(String value) {
return Optional.ofNullable(value)
.flatMap(dv -> Arrays.stream(NotificationType.values())
.filter(ev -> dv.equals(ev.value))
.findFirst());
}
Create the required converter:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
#Converter(autoApply = true)
public class NotificationTypeConverter implements AttributeConverter<NotificationType, String> {
#Override
public String convertToDatabaseColumn(NotificationType notificationType) {
return null == notificationType ? null : notificationType.value;
}
#Override
public NotificationType convertToEntityAttribute(String databaseValue) {
return NotificationType.getFromValue(databaseValue)
.orElse(null);
}
}
And now, you only have to modify your model:
#Entity
#Table
public class MyEntity {
#Convert(converter=NotificationTypeConverter.class)
private NotificationType notificationType;
}

How to find data in database in Spring Boot by my variable?

I have this code:
#GetMapping("/notes/{id}")
public ResponseEntity<Note> getNoteById(#PathVariable(value = "id") Long id) {
Note note = noteRepository.findOne(id);
if(note == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok().body(note);
}
So this method just finds information by the sent id.
The method noteRepository.findOne() accepts only Long or class extends org.springframework.data.domain.Example
I want to retrive data by my own variable "secretkey" (String). How can I do this?
Use this method :
#GetMapping("/notes/byKey/{secretKey}")
public ResponseEntity<Note> getNoteById(#PathVariable(value = "secretKey") String secretKey) {
Note note = noteRepository.findBySecretKey(secretKey);
if(note == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok().body(note);
}
in your NoteRepositoryclass add this method:
Note findBySecretKey(String secretKey);
note that I also changed the request path.
Assume that you are using Spring Data:
#Entity
#Table(name="note")
public class Note {
#Id
#GeneratedAuto
private Long id;
#Column(name="secretKey")
private String secretKey;
// getters, setters
}
//Repository
public interface NoteRepository extends JpaRepository<Note, Long> {
List<Note> findBySecretKey(String secretKey);
}
// Controller
#GetMapping(path = "/notes/secretKey/{secretKey}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Note> getNoteBySecretKey(
#PathVariable(value = "secretKey") String secretKey) {
Note note = noteRepository.findBySecretKey(secretKey);
if(note == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok().body(note);
}

How to map multiple parameter names to POJO when binding spring mvc command objects

My question is actually a spin-off of this question as seen here... so it might help to check that thread before proceeding.
In my Spring Boot project, I have two entities Sender and Recipient which represent a Customer and pretty much have the same fields, so I make them extend the base class Customer;
Customer base class;
#MappedSuperclass
public class Customer extends AuditableEntity {
#Column(name = "firstname")
private String firstname;
#Transient
private CustomerRole role;
public Customer(CustomerRole role) {
this.role = role;
}
//other fields & corresponding getters and setters
}
Sender domain object;
#Entity
#Table(name = "senders")
public class Sender extends Customer {
public Sender(){
super.setRole(CustomerRole.SENDER);
}
}
Recipient domain object;
#Entity
#Table(name = "recipients")
public class Recipient extends Customer {
public Recipient(){
super.setRole(CustomerRole.RECIPIENT);
}
}
NOTE - Sender and Recipient are exactly alike except for their roles. These can be easily stored in a single customers Table by making the Customer base class an entity itself, but I intentionally separate the entities this way because I have an obligation to persist each customer type in separate database tables.
Now I have one form in a view that collects details of both Sender & Recipient, so for example to collect the firstname, I had to name the form fields differently as follows;
Sender section of the form;
<input type="text" id="senderFirstname" name="senderFirstname" value="$!sender.firstname">
Recipient section of the form;
<input type="text" id="recipientFirstname" name="recipientFirstname" value="$!recipient.firstname">
But the fields available for a customer are so many that I'm looking for a way to map them to a pojo by means of an annotation as asked in this question here. However, the solutions provided there would mean that I have to create separate proxies for both domain objects and annotate the fields accordingly e.g
public class SenderProxy {
#ParamName("senderFirstname")
private String firstname;
#ParamName("senderLastname")
private String lastname;
//...
}
public class RecipientProxy {
#ParamName("recipientFirstname")
private String firstname;
#ParamName("recipientLastname")
private String lastname;
//...
}
So I got very curious and was wondering, is there a way to map this Proxies to more than one #ParamName such that the base class for example can just be annotated as follows?;
#MappedSuperclass
public class Customer extends AuditableEntity {
#Column(name = "firstname")
#ParamNames({"senderFirstname", "recipientFirstname"})
private String firstname;
#Column(name = "lastname")
#ParamNames({"senderLastname", "recipientLastname"})
private String lastname;
#Transient
private CustomerRole role;
public Customer(CustomerRole role) {
this.role = role;
}
//other fields & corresponding getters and setters
}
And then perhaps find a way to select value of fields based on annotation??
A suggestion from Zhang Jie like ExtendedBeanInfo
so i do it this way
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Alias {
String[] value();
}
public class AliasedBeanInfoFactory implements BeanInfoFactory, Ordered {
#Override
public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
return supports(beanClass) ? new AliasedBeanInfo(Introspector.getBeanInfo(beanClass)) : null;
}
private boolean supports(Class<?> beanClass) {
Class<?> targetClass = beanClass;
do {
Field[] fields = targetClass.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Alias.class)) {
return true;
}
}
targetClass = targetClass.getSuperclass();
} while (targetClass != null && targetClass != Object.class);
return false;
}
#Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 100;
}
}
public class AliasedBeanInfo implements BeanInfo {
private static final Logger LOGGER = LoggerFactory.getLogger(AliasedBeanInfo.class);
private final BeanInfo delegate;
private final Set<PropertyDescriptor> propertyDescriptors = new TreeSet<>(new PropertyDescriptorComparator());
AliasedBeanInfo(BeanInfo delegate) {
this.delegate = delegate;
this.propertyDescriptors.addAll(Arrays.asList(delegate.getPropertyDescriptors()));
Class<?> beanClass = delegate.getBeanDescriptor().getBeanClass();
for (Field field : findAliasedFields(beanClass)) {
Optional<PropertyDescriptor> optional = findExistingPropertyDescriptor(field.getName(), field.getType());
if (!optional.isPresent()) {
LOGGER.warn("there is no PropertyDescriptor for field[{}]", field);
continue;
}
Alias alias = field.getAnnotation(Alias.class);
addAliasPropertyDescriptor(alias.value(), optional.get());
}
}
private List<Field> findAliasedFields(Class<?> beanClass) {
List<Field> fields = new ArrayList<>();
ReflectionUtils.doWithFields(beanClass,
fields::add,
field -> field.isAnnotationPresent(Alias.class));
return fields;
}
private Optional<PropertyDescriptor> findExistingPropertyDescriptor(String propertyName, Class<?> propertyType) {
return propertyDescriptors
.stream()
.filter(pd -> pd.getName().equals(propertyName) && pd.getPropertyType().equals(propertyType))
.findAny();
}
private void addAliasPropertyDescriptor(String[] values, PropertyDescriptor propertyDescriptor) {
for (String value : values) {
if (!value.isEmpty()) {
try {
this.propertyDescriptors.add(new PropertyDescriptor(
value, propertyDescriptor.getReadMethod(), propertyDescriptor.getWriteMethod()));
} catch (IntrospectionException e) {
LOGGER.error("add field[{}] alias[{}] property descriptor error", propertyDescriptor.getName(),
value, e);
}
}
}
}
#Override
public BeanDescriptor getBeanDescriptor() {
return this.delegate.getBeanDescriptor();
}
#Override
public EventSetDescriptor[] getEventSetDescriptors() {
return this.delegate.getEventSetDescriptors();
}
#Override
public int getDefaultEventIndex() {
return this.delegate.getDefaultEventIndex();
}
#Override
public PropertyDescriptor[] getPropertyDescriptors() {
return this.propertyDescriptors.toArray(new PropertyDescriptor[0]);
}
#Override
public int getDefaultPropertyIndex() {
return this.delegate.getDefaultPropertyIndex();
}
#Override
public MethodDescriptor[] getMethodDescriptors() {
return this.delegate.getMethodDescriptors();
}
#Override
public BeanInfo[] getAdditionalBeanInfo() {
return this.delegate.getAdditionalBeanInfo();
}
#Override
public Image getIcon(int iconKind) {
return this.delegate.getIcon(iconKind);
}
static class PropertyDescriptorComparator implements Comparator<PropertyDescriptor> {
#Override
public int compare(PropertyDescriptor desc1, PropertyDescriptor desc2) {
String left = desc1.getName();
String right = desc2.getName();
for (int i = 0; i < left.length(); i++) {
if (right.length() == i) {
return 1;
}
int result = left.getBytes()[i] - right.getBytes()[i];
if (result != 0) {
return result;
}
}
return left.length() - right.length();
}
}
}

Make input parameter mandatory JAX-WS

Input paramter to my webservice method is an Object of Class AddSingleDocRequest. This class contains all the input fields as class instance variable with their getter and setter. I want to make some of the input fields mandatory. What is the best way to achieve this ?
Following is the code snippet:
**//webservice method
public String uploadDoc(AddSingleDocRequest request)
{
}
**//Request Class**
public class AddSingleDocRequest
{
private String sFilepath;
private String sDataClass;
public void setDataClassName(String dataClassName)
{
this.sDataClass= dataClassName;
}
public String getDataClassName() {
return sDataClass;
}
public void setFilePath(String filePath)
{
this.sFilepath=filePath;
}
public String getFilePath()
{
return sFilepath;
}
}
I want to make sFilePath parameter as mandatory.
Add the next JAX-B annotations:
#XmlType(name = "AddSingleDocRequestType", propOrder = {
"sFilepath", "sDataClass"
})
public class AddSingleDocRequest {
#XmlElement(name = "sFilepath", required = true)
private String sFilepath;
#XmlElement(name = "sDataClass", required = false)
private String sDataClass;
public void setDataClassName(String dataClassName) {
this.sDataClass = dataClassName;
}
public String getDataClassName() {
return sDataClass;
}
public void setFilePath(String filePath) {
this.sFilepath = filePath;
}
public String getFilePath() {
return sFilepath;
}
}
See more in Using JAXB to customize mapping for JAX-WS web services.

Categories

Resources