How not to make a framework for default request values in Spring
Tags: java, refactorit, opensource, webdev
Published: 2020-04-07 12:10:21

I used to look for common solutions for the problems I face. And I fail sometimes within my researches of the best solution like many developers. But it's still important to describe your path for the future researcher.

### Problem

<br>There are a lot of written Spring REST API services with a lot of common code lines like converters, formatters, custom marshallers, etc. But it is a question still:
>How to make dynamic Spring default values for composite objects input into a REST controller?

There are a lot of questions and discussions about it [[1]](#problem-links)

### What do we have?

1. Default values for request parameters and request headers

    ```java
    @RequestParam(defaultValue = "19") Integer age,
    @RequestHeader(defaultValue = "John Snow") String name
    ```
2. Dynamic values for request parameters and request headers

    ```java
    @RequestParam(defaultValue = "${some.property.default.age}") Integer size,
    @RequestHeader(defaultValue = "${some.property.default.name}") String name
    ```
3. Query parameters collection into a custom object. It doesnt require `RequestParam` annotation and doesnt work with headers by default
    ```java
    @GetMapping("/characters")
    public Character getCharacter(Character character)
    ```
    DTO class:
    ```java
    @Data
    public class Character {
        private String name;
        private Integer age;
    }
    ```
    Example call:
    ```
    curl --location --request GET 'http://localhost:8080/characters? age=19&name=John%20Snow'
    ```
4. Request body collection into a custom object
    ```java
    @PostMapping("/characters")
    public Character getCharacter(@RequestBody Character character)
    ```

In this way, we can customize default values or dynamic one by Spring properties in cases of:

|                  | Default values | Dynamic default values |
|------------------|----------------|------------------------|
| Query            | TRUE           | TRUE                   |
| Header           | TRUE           | TRUE                   |
| Composite query  | TRUE           | FALSE                  |
| Composite header | TRUE           | FALSE                  |
| Body             | TRUE           | FALSE                  |

### What do we need?

The most rational way is to have a mechanism to put default values by annotation/annotation's parameters as we do with default values in annotations: `@Value`, `@RequestParam`, `@RequestHeader`

Requirements:
* The Jackson library must have an opportunity for default values. There is an open [GitHub ticket](https://github.com/FasterXML/jackson-modules-base/issues/23) and a note at the [@JsonProperty documentation](https://fasterxml.github.io/jackson-annotations/javadoc/2.6/com/fasterxml/jackson/annotation/JsonProperty.html#defaultValue())

>**Documentation**: It is possible that in future this annotation could be used for value defaulting, and especially for default values of Creator properties, since they support required() in 2.6 and above.

* Spring must provide custom properties to _Jackson_ module. For example:
```yaml
spring.servlet:
    defaults:
      unit-request.value:
        name: Artem
```

### Workaround with a decorator class

There is a workaround for **single** cases to have an object of default values. <br>The hard part that we have to verify nullable values at **every** field and then use a field from another one.

1. Have a common interface because we'll write a wrapper:
    ```java
    public interface Unit {
        String getName();
    
        Long getId();
    }
    ```

2. Request body implementation class:
    ```java
    @Data
    public class UnitRequest implements Unit {
        @NotNull
        private String name;
        @Nullable
        private Long id;
    }
    ```
3. Declare default properties:
    ```yml
    spring.servlet:
        defaults:
          unit-request.value:
            name: Artem
    ```
4. Declare a class for default properties:
    ```java
    @Data
    public class DefaultProperties<T> {
        @Nullable
        private T value;
    }
    ```
5. Declare a configuration properties bean. Please notice, that I suggest to **reuse** the same class `UnitRequest` for properties because it is the most rational way to reuse **existed** class behavior. The other way - create another implementation:

    ```java
    @Configuration
    public class DefaultPropertiesConfiguration {
        
        @Bean
        @ConfigurationProperties("spring.servlet.defaults.unit-request")
        public DefaultProperties<UnitRequest> unitRequestDefaultProperties() {
            return new DefaultProperties<>();
        }
    }
    ```
6. Create a wrapper class to use values from another object on null cases:
    ```java
    public class UnitRequestWrapper implements Unit {
    
        private final Unit decorated;
        private final Unit defaultUnit;
    
        public UnitRequestWrapper(Unit decorated, Unit defaultUnit) {
            this.decorated = decorated;
            this.defaultUnit = defaultUnit;
        }
    
        @Override
        public String getName() {
            String actual = decorated.getName();
            return actual == null ? defaultUnit.getName() : actual;
        }
    
        @Override
        public Long getId() {
            Long actual = decorated.getId();
            return actual == null ? defaultUnit.getId() : actual;
        }
    }
    ```

7. Change your `RestController` code
    ```java
    private final DefaultProperties<UnitRequest> unitRequestDefaultProperties; //custom
    
    @PostMapping("/units")
    public UnitResponse post(@RequestBody UnitRequest unitRequest) {
        Unit unit = new UnitRequestWrapper(unitRequest, unitRequestDefaultProperties.getValue()); //custom
        return UnitResponse
                .builder()
                .id(1L)
                .name(unit.getName())
                .subUnit(unit.getSubUnit())
                .build();   
   }
    ```

### Workaround with a proxy class
This solution is very close to a reusable one and could be a part of another solution.
1. Proxy methods of web request DTO to call the methods from another object on null values:
    ```java
    public class NullableProxyFactory {

    public <T> T getProxy(Class<? super T> type, T first, T second) {
        return (T) Proxy.newProxyInstance(
                type.getClassLoader(),
                new Class[]{type},
                new NullableInvocationHandler<>(first, second)
        );
    }}
    ```
2. Invocation Handler code:
    ```java
    public class NullableInvocationHandler<T> implements InvocationHandler {
        private final T first;
        private final T second;
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Method m = findMethod(second.getClass(), method);
            if (m != null) {
                Object invocationResult = m.invoke(first, args);
                if (invocationResult == null) {
                    return m.invoke(second, args);
                }
                return invocationResult;
            }
            return null;
        }
    
        private Method findMethod(Class<?> clazz, Method method) {
            try {
                return clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
            } catch (NoSuchMethodException e) {
                return null;
            }
        }
    }
    ```
3. Usage:
    ```java
    @PostMapping("/units")
        public UnitResponse post(@RequestBody UnitRequest unitRequest) {
            Unit unit = proxyFactory.getProxy(Unit.class, unitRequest, unitRequestDefaultProperties.getValue());
    ...
    }
    ```

### Pros
1. We have only one additional line inside the controller
2. The Proxy solution looks interesting for reusable cases. We could not write boilerplate code for field values verification.
3. We can take inside a library three classes from the proxy example.

### Cons
1. It will take many more lines of code to proxy the value of the internal composite fields. Like when `UnitRequest` has a field `SubUnit`.
2. It requires modifying the controller code and this is redundant cause we have a lot of Spring and Jackson code from above.
3. We have to write boilerplate code inside our _wrapper_.
4. The Proxy example is somehow applicable to dynamic query parameters and dynamic headers but the Wrapper example not so much.

### What I have tried also?

* AOP. I've written an aspect to interact with all the calls for getter methods with my custom annotation. The annotation is needed to reduce method quantity.
But there is a complicated moment with [Spring bean injection into aspect](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-atconfigurable). You have to put `@Configurable` on every DTO class plus AOP code seemed too risky, I've got a lot of problems on this way.
* `@ControllerAdvice` + `@InitBinder`. It works for single cases also. You either override all the fields or have to verify (as in my workaround above) all the fields values
* Proxy + `BeanDeserializerModifier`. It is possible to wrap the result of _Jackson_ deserialization method. I have wrapped the result by proxy object but got the `java.lang.IllegalArgumentException: argument type mismatch` exception. The `Proxy` type object came to the controller it is obviously unresolvable.

#### Conclusions

I wanted to share the unsuccessful example of software solution research in Java backend world. It helps me to understand better the Jackson library and Proxy mechanisms.

Keep researching the best solution. It broadens the mind on the way.

Hope this article describes the question fully.

#### Problem links
1. https://github.com/FasterXML/jackson-databind/issues/1420
2. https://stackoverflow.com/questions/18805455/setting-default-values-to-null-fields-when-mapping-with-jackson
3. https://stackoverflow.com/questions/32587551/how-to-make-requestparam-configurable-through-properties-file
4. https://stackoverflow.com/questions/42329235/how-to-provide-default-values-for-array-parameters-in-spring-mvc-url-mapping
5. https://stackoverflow.com/questions/15213752/spring-requestbody-and-default-values
6. https://stackoverflow.com/questions/20469938/spring-boot-and-mvc-how-to-set-default-value-for-requestbody-object-fields-fro
7. https://stackoverflow.com/questions/38882639/handle-null-and-default-values-in-requestbody-with-jackson
8. https://stackoverflow.com/questions/58053179/spring-jacksonrest-modify-request-body-before-reaching-to-controller-add
9. https://stackoverflow.com/questions/18088955/markdown-continue-numbered-list