16

Consider the following POJOs:

public class SchedulePayload {
    public String name;
    public String scheduler;
    public PeriodPayload notificationPeriod;
    public PeriodPayload schedulePeriod;
}

private class Lecture {
    public ZonedDateTime start;
    public ZonedDateTime end;
}

public class XmlSchedule {
    public String scheduleName;
    public String schedulerName;
    public DateTime notificationFrom;
    public DateTime notificationTo;
    public DateTime scheduleFrom;
    public DateTime scheduleTo;
}

public class PeriodPayload {
    public DateTime start;
    public DateTime finish;
}

Using MapStruct, I created a mapper that maps XmlSchedule to a SchedulePayload. Due to "business" "logic", I need to constrain notificationPeriod and schedulePeriod to a Lecture's start and end field values. Here is what I've come up to, using another class:

@Mapper(imports = { NotificationPeriodHelper.class })
public interface ISchedulePayloadMapper
{
    @Mappings({
        @Mapping(target = "name", source = "scheduleName"),
        @Mapping(target = "scheduler", source = "schedulerName"),
        @Mapping(target = "notificationPeriod", expression = "java(NotificationPeriodHelper.getConstrainedPeriod(xmlSchedule, notificationFrom, notificationTo))"),
        @Mapping(target = "schedulePeriod", expression = "java(NotificationPeriodHelper.getConstrainedPeriod(xmlSchedule, scheduleFrom, scheduleTo))")
    })
    SchedulePayload map(XmlSchedule xmlSchedule, Lecture lecture);

}

Is there any way this can be achieved in another way (i.e. another mapper, decorators, etc.)? How can I pass multiple values (xmlSchedule, lecture) to a mapper?

giannoug
  • 643
  • 3
  • 8
  • 20

1 Answers1

25

What you can do is create an @AfterMapping method to populate those parts manually:

@Mapper
public abstract class SchedulePayloadMapper
{
    @Mappings({
        @Mapping(target = "name", source = "scheduleName"),
        @Mapping(target = "scheduler", source = "schedulerName"),
        @Mapping(target = "notificationPeriod", expression = "java(NotificationPeriodHelper.getConstrainedPeriod(xmlSchedule, notificationFrom, notificationTo))"),
        @Mapping(target = "schedulePeriod", expression = "java(NotificationPeriodHelper.getConstrainedPeriod(xmlSchedule, scheduleFrom, scheduleTo))")
    })
    public abstract SchedulePayload map(XmlSchedule xmlSchedule, Lecture lecture);

    @AfterMapping
    protected void addPeriods(@MappingTarget SchedulePayload result, XmlSchedule xmlSchedule, Lecture lecture) {
        result.setNotificationPeriod(..);
        result.setSchedulePeriod(..);
    }
}

Alternatively, you can place the @AfterMapping method in another class that is referenced in @Mapper(uses = ..) or you can use a Decorator (using the mechanisms MapStruct provides, or of your dependency injection framework if you use one).

agudian
  • 700
  • 6
  • 6
  • 1
    `@AfterMapping` seems a clean approach! Thanks! – giannoug Jun 09 '16 at 09:39
  • Just out of curiosity, is there a way to handle immutable objects? – Robert Gabriel Aug 09 '18 at 15:02
  • @RobertGabriel since the latest (1.3.0.Beta1) MapStruct can use Builders to map into Immutable objects. In the answer of this question, this would mean that you can have the builder for `SchedulePayload` in the `@AfterMapping` – Filip Sep 17 '18 at 05:50
  • 1
    Yes, you are right. A colleague told me the same thing yesterday. :) – Robert Gabriel Sep 18 '18 at 12:13
  • 3
    Note when using the builder pattern in your objects, the `@MappingTarget` should be of type `YourBuilder` instead of your response type! – Jacob van Lingen Jun 28 '19 at 09:41
  • Once you use @AfterMapping you dont need these lines anymore: @Mapping(target = "notificationPeriod", expression = "java(NotificationPeriodHelper.getConstrainedPeriod(xmlSchedule, notificationFrom, notificationTo))"), @Mapping(target = "schedulePeriod", expression = "java(NotificationPeriodHelper.getConstrainedPeriod(xmlSchedule, scheduleFrom, scheduleTo))") }) – Philippe Simo Apr 23 '20 at 13:00