1. Overview

When using Orika, we generally map mutable JavaBean objects that contain getters and setters. But Orika also provides support to work with the immutable types. In this tutorial, we're going to look at how we can map immutable types using Orika.

2. Sample Application

Let's start with the sample application.

We'll work with the Car and CarDto classes:

public class Car {

    private final String brand;
    private final String horsePower;

    public Car(String brand, String horsePower) {
        this.brand = brand;
        this.horsePower = horsePower;
    }

    // Getters...
}

public class CarDto {

    private final String brand;
    private final String power;
    private final String year;

    public CarDto(String brand, String power) {
        this.brand = brand;
        this.power = power;
        this.year = null;
    }

    public CarDto(String brand, String power, String year) {
        this.brand = brand;
        this.power = power;
        this.year = year;
    }

    // Getters...
}

Note that these classes are immutable and they don't have any setter methods. Also, the CarDto class defines an additional property year that doesn't have a matching counterpart in Car.

3. Default Behavior

With the default configuration, Orika tries to select the best matching constructor to instantiate an immutable object:

public BoundMapperFacade<Car, CarDto> mapWithConstructor() {
    final DefaultMapperFactory mapperFactory = new Builder().build();
    mapperFactory.classMap(Car.class, CarDto.class)
      .field("horsePower", "power")
      .byDefault()
      .register();
    return mapperFactory.getMapperFacade(Car.class, CarDto.class);
}

Here, we have a mapper for the Car and CarDto classes where we define the field mappings. Although we aren't telling Orika which constructor to use, it selects the best matching constructor using the available properties:

@Test
public void testMapWithConstructor() {
    final BoundMapperFacade<Car, CarDto> mapper = configuration.mapWithConstructor();
    final Car car = new Car("Ferrari", "950");

    final CarDto carDto = mapper.map(car);

    assertThat(carDto.getBrand()).isEqualTo(car.getBrand());
    assertThat(carDto.getPower()).isEqualTo(car.getHorsePower());
    assertThat(carDto.getYear()).isNull();
}

With this test, we confirm that Orika selects the constructor with the following signature:

public CarDto(String brand, String power) {
    this.brand = brand;
    this.power = power;
    this.year = null;
}

Notice the parameter names, brand, and power. They match the field names we defined in the mapper configuration. power has an explicit field mapping since we invoke field("horsePower", "power"). And brand has an implicit mapping because of the byDefault invocation.

Now we'll rename the constructor parameters:

public CarDto(String brand, String powerValue) {
    this.brand = brand;
    this.power = powerValue;
    this.year = null;
}

In this version, the power parameter becomes powerValue. When we run the test again, it fails since powerValue isn't defined in the field mappings:

ma.glasnost.orika.MappingException: While attempting to generate ObjectFactory using constructor 
'public com.javabyexamples.java.mapper.orika.immutable.CarDto(java.lang.String,java.lang.String)', 
an automatic mapping of the source type ('Car') to this constructor call could not be determined. 
Please register a custom ObjectFactory implementation which is able to create an instance of 'CarDto' from an instance of 'Car'.

We'll conclude that it's best to declare the constructor parameter names the same as the properties.

4. Selecting Constructor

Next, we'll select the constructors explicitly for instantiating mapped objects. For this purpose, we'll use the constructorA and constructorB methods.

public BoundMapperFacade<Car, CarDto> mapWithConstructorSelection() {
    final DefaultMapperFactory mapperFactory = new Builder().build();
    mapperFactory.classMap(Car.class, CarDto.class)
      .field("horsePower", "power")
      .constructorA("brand", "horsePower")
      .constructorB("brand", "power")
      .byDefault()
      .register();
    return mapperFactory.getMapperFacade(Car.class, CarDto.class);
}

With constructorA("brand", "horsePower"), we're telling Orika to use the constructor with the brand and horsePower parameters for the Car class. Similarly, the constructorB invocation specifies the constructor for the CarDto class.

5. Using ObjectFactory

Another way to instantiate an immutable type is to provide an ObjectFactory implementation. The factory class enables us to customize the construction process in that we can provide constant values or create other helper objects.

For example, CarDto declares the year field and we don't have a matching field in Car. We'll now specify a constant value using an ObjectFactory:

public class CarDtoFactory implements ObjectFactory<CarDto> {

    @Override
    public CarDto create(Object source, MappingContext mappingContext) {
        if (source instanceof Car) {
            final Car car = (Car) source;
            return new CarDto(car.getBrand(), car.getHorsePower(), "2000");
        }

        return null; 
    }
}

Here, we're constructing a CarDto instance, if the source is Car. Additionally, we're providing "2000" as the year argument.

For this ObjectFactory to take effect, we must register it using the MapperFactory.registerObjectFactory method.

public BoundMapperFacade<Car, CarDto> mapWithObjectFactory() {
    final DefaultMapperFactory mapperFactory = new Builder().build();
    mapperFactory.registerObjectFactory(new CarDtoFactory(), CarDto.class);
    mapperFactory.classMap(Car.class, CarDto.class)
      .field("horsePower", "power")
      .byDefault()
      .register();
    return mapperFactory.getMapperFacade(Car.class, CarDto.class);
}

With this configuration, Orika uses CarDtoFactory to instantiate a CarDto object - instead of directly calling a CarDto constructor.

6. Using PassThroughConverter

In some cases, the source and target objects use the same type. Assume that we have the Driver and DriverDto classes:

public class Driver {

    private final Car car;

    public Driver(Car car) {
        this.car = car;
    }

    // Getters...
}

public class DriverDto {

    private final Car car;

    public DriverDto(Car car) {
        this.car = car;
    }

    // Getters...
}

They both have a car field of type Car. So unlike the other examples, we aren't mapping Car to CarDto.

Since immutability guarantees that any modification attempt to the state results in a new instance, we can just let the source and target use the same object. Orika provides the PassThroughConverter class that copies objects by reference. It performs this operation for a given set of types:

public BoundMapperFacade<Driver, DriverDto> mapWithPassThroughConverter() {
    final DefaultMapperFactory mapperFactory = new Builder().build();
    mapperFactory.getConverterFactory().registerConverter(new PassThroughConverter(Car.class));
    mapperFactory.classMap(Driver.class, DriverDto.class)
      .byDefault()
      .register();
    return mapperFactory.getMapperFacade(Driver.class, DriverDto.class);
}

Since we want to copy the Car instances by reference, we're invoking new PassThroughConverter(Car.class). Then we're registering it with the MapperFactory instance.

After the mapping, the Driver and DriverDto objects reference the same Car instance:

@Test
public void testMapWithPassThroughConverter() {
    final BoundMapperFacade<Driver, DriverDto> mapper = configuration.mapWithPassThroughConverter();
    final Car car = new Car("Ferrari", "950");
    final Driver driver = new Driver(car);

    final DriverDto driverDto = mapper.map(driver);

    assertThat(driverDto.getCar() == driver.getCar()).isTrue();
}

7. Summary

In this tutorial, we've looked at how we can map immutable objects using the Orika JavaBean mapping library. Firstly we investigated the constructor detection and selection support. Then we used ObjectFactory to further control the construction process. Lastly, we saw that we can use the same immutable object instead of creating a new one.

As always, the source code for all examples is available on Github.