Can I configure Orika to disregard return type of setter

120 Views Asked by At

Since a version upgrade from an unrelated library, our DTOs have fluent setters. Basically a nice thing, but now Orika is failing to map properties

public class DebugOrikaTest {

  @Test
  public void simpleToFluent() {
    final MapperFacade mapper = new ConfigurableMapper();
    final SimpleWithBoolean a = new SimpleWithBoolean();
    a.setFoo(Boolean.TRUE);
    a.setBar("foobar");
    final FluentWithBoolean b = new FluentWithBoolean();
   // act
    mapper.map(a, b);
    // assert
    Assertions.assertEquals("foobar", b.getBar());
    Assertions.assertTrue(b.isFoo());
  }

  @Test
  public void simpleToOther() {
    final MapperFacade mapper = new ConfigurableMapper();
    final SimpleWithBoolean a = new SimpleWithBoolean();
    a.setFoo(Boolean.TRUE);
    a.setBar("foobar");
    final OtherWithBoolean b = new OtherWithBoolean();
    // act
    mapper.map(a, b);
    // assert
    Assertions.assertEquals("foobar", b.getBar());
    Assertions.assertTrue(b.isFoo());
  }

  public static class SimpleWithBoolean {
    private Boolean foo;
    private String bar;

    public Boolean isFoo() {
      return foo;
    }

    public void setFoo(Boolean foo) {
      this.foo = foo;
    }

    public String getBar() {
      return bar;
    }

    public void setBar(String bar) {
      this.bar = bar;
    }
  }

  public static class FluentWithBoolean {
    private Boolean foo;
    private String bar;

    public Boolean isFoo() {
      return foo;
    }

    public FluentWithBoolean setFoo(Boolean foo) {
      this.foo = foo;
      return this;
    }

    public String getBar() {
      return bar;
    }

    public FluentWithBoolean setBar(String bar) {
      this.bar = bar;
      return this;
    }
  }

  public static class OtherWithBoolean {
    private Boolean foo;
    private String bar;

    public Boolean isFoo() {
      return foo;
    }

    public void setFoo(Boolean foo) {
      this.foo = foo;
    }

    public String getBar() {
      return bar;
    }

    public void setBar(String bar) {
      this.bar = bar;
    }
  }

}

The simpleToOther test is green, but simpleToFluent fails. Is there a way to configure Orika to map standard JavaBean setters to fluent setters?

1

There are 1 best solutions below

0
Simon On BEST ANSWER

The culprit (at least in Orika 1.5.2) is the IntrospectorPropertyResolver, which uses java.beans:

BeanInfo beanInfo = Introspector.getBeanInfo(type);
PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();

this will not return properties with a fluent setter. The following subclass uses Apache beanutils, which has a FluentPropertyBeanIntrospector:

public class LenientIntrospectorPropertyResolver extends IntrospectorPropertyResolver {

  private boolean includeTransientFields;

  public LenientIntrospectorPropertyResolver(boolean includePublicFields) {
    super(includePublicFields);
  }

  public LenientIntrospectorPropertyResolver(boolean includePublicFields, boolean includeTransientFields) {
    super(includePublicFields, includeTransientFields);
    this.includeTransientFields = includeTransientFields;
  }

  public LenientIntrospectorPropertyResolver() {
  }

  @Override
  protected void collectProperties(Class<?> type, Type<?> referenceType, Map<String, Property> properties) {
    PropertyUtils.addBeanIntrospector(new FluentPropertyBeanIntrospector());
    final PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(type);
    for (final PropertyDescriptor pd : descriptors) {
      processPropertyDescriptor(type, referenceType, properties, pd);
    }
  }

  private void processPropertyDescriptor(Class<?> type, Type<?> referenceType, Map<String, Property> properties, PropertyDescriptor pd) {
    try {
      Method readMethod = PropertyUtils.getReadMethod(pd);
      if (readMethod == null && Boolean.class.equals(pd.getPropertyType())) {
        /*
         * Special handling for Boolean "is" read method; not strictly
         * compliant with the JavaBeans specification, but still very common
         */
        try {
          readMethod = type.getMethod("is" + capitalize(pd.getName()));
        } catch (NoSuchMethodException e) {
          readMethod = null;
        }
      }
      if (!includeTransientFields && (readMethod != null) && (readMethod.getAnnotation(Transient.class) != null)) {
        return;
      }
      final Method writeMethod = PropertyUtils.getWriteMethod(pd);
      processProperty(pd.getName(), pd.getPropertyType(), readMethod, writeMethod, type, referenceType, properties);
    } catch (final Exception e) {
      throw new RuntimeException("Unexpected error while trying to resolve property " + referenceType.getCanonicalName() + ", [" + pd.getName() + "]", e);
    }
  }

}

The PropertyResolver then needs to be registered

factoryBuilder.propertyResolverStrategy(new LenientIntrospectorPropertyResolver());