{{announcement.body}}
{{announcement.title}}

Spring Web Service Response Filtering

DZone 's Guide to

Spring Web Service Response Filtering

For large projects, you must control the serialization of a service response.

· Java Zone ·
Free Resource

Image title

Introduction

Sometimes, mostly in large projects, there is a need to control the serialization of a service response — especially to filter fields of an object in response. For solving this type of issue, we have several solutions. Let’s take a look.

Issue

For example, we have an object named “User” that contains fields like: id, email, fullName, password, secretKey. We have a task to return this object to a recipient with different fields depending on the role in the session. Also, we have two types of roles: admin and user:

  1. When the recipient has an “admin” role, we should return the full object with the fields: id, email, fullName, password, secretKey.
  2. When a recipient has a “user” role, we should return an object partially with the fields: email, fullName.

Standard Solutions

  1. Inherit two different classes with a different set of fields from the User class and call them like: UserAdmin, UserSimple.
  2. Reset fields of the User class in response to the method depending on the role.
  3. Then, return the recipient to a serialized string with removed fields depending on role instead of User class.

Maybe this solution looks simple and justified, but it is not the best. In some situations, it will increase the number of similar classes or will lead to unpredictability of the service execution. But in all solutions, we need to create additional logic code.

JFilter Solution

This approach offers another solution. We can leave the service’s method response unchanged. But we need to somehow get the Spring message converter work. To let him know how to serialize an object and filter/exclude fields, the JFilter module is designed to solve this kind of issue.

Let’s look at the diagram.

The diagram of module flowchart

As you can see, the JFilter module is embedded between Spring Web Service and Send response methods. The description of internal parts of the module include:

  1.  FilterAdvice— is a ControllerAdvice component that handles all responses from the Spring Web Service
  2.  FilterProvider — is a component that attempts to find a suitable filter
  3.  FilterConverter — is a class that attempts to provide a suitable JSON or XML serializer
  4. After serialization, the response will be written to the HttpOutputMessage body

Using

Before discussing filter types, we should know how to use them in your project. So, first of all, we need to import the repository:

<dependency>
    <groupId>com.github.rkonovalov</groupId>
    <artifactId>json-ignore</artifactId>
    <version>1.0.8</version>
</dependency>


If you are using other automation tools, you can find a declaration by following this link.

The next step is enabling filter handling:

@ComponentScan({"com.jfilter.components"})
@EnableJsonFilter


This annotation should be declared in the Spring Web Service configuration component. The following code shows you how you can declare it:

@Configuration
@EnableWebMvc
@ComponentScan({"com.jfilter.components"})
@EnableJsonFilter
public class AppConfig extends WebMvcConfigurerAdapter {

}


After these two simple steps, we can start to explore filter types. But before that, we also need to define a sample Spring Rest component with the response method:

@RestController
public class SessionService {
    @Autowired
    private UserController userController;   

    @RequestMapping(value = "/users/signIn",
            params = {"email", "password"}, method = RequestMethod.POST,
            consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
            produces = {MediaType.APPLICATION_JSON_VALUE})            
    public User signIn(@RequestParam("email") String email, @RequestParam("password") String password) {
        return userController.signInUser(email, password);
    }
}


This method returns the object instance of the User class. There is a JSON serialized object that will be sent to the recipient.

{
  "id": 10,
  "email": "janedoe@gmail.com", 
  "fullName": "Jane Doe",
  "password": "12345",
  "secretKey": "54321",
  "address": {
    "id": 15,
    "apartmentNumber": 22,
    "street": {
      "id": 155,
      "streetName": "Bourbon Street",
      "streetNumber": 15
    }
  }
}


That’s it. Now, we can declare the filter annotations.

Filters

Filters mainly analyze the Spring Service response for a filter annotation, and if the annotation is configured, it attempts to generate a list of filterable/exclusion fields from the response object. Let’s look at them a little closer.

Field Filter

This is a simple filter annotation where you can to define filterable fields of an object.

Annotation Example

@FieldFilterSetting(className = User.class, fields = {"id", "password", "secretKey"})


Code Example

@FieldFilterSetting(className = User.class, fields = {"id", "password", "secretKey"})
    @RequestMapping(value = "/users/signIn",
            params = {"email", "password"}, method = RequestMethod.POST,
            consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
            produces = {MediaType.APPLICATION_JSON_VALUE})            
    public User signIn(@RequestParam("email") String email, @RequestParam("password") String password) {
        return userController.signInUser(email, password);
    }


Response Result

{ 
  "email": "janedoe@gmail.com", 
  "fullName": "Jane Doe",
  "address": {
    "id": 15,
    "apartmentNumber": 22,
    "street": {
      "id": 155,
      "streetName": "Bourbon Street",
      "streetNumber": 15
    }
  }
}


As you can see, fields id, password, and secretKey have been successfully filtered from the response.

If you want to filter fields from a complex class that contains sub-classes with the same field names, you can filter them without qualifications for a certain class name.

Annotation Example

@FieldFilterSetting(fields = {"id", "password", "secretKey"})


Response Result

{ 
  "email": "janedoe@gmail.com", 
  "fullName": "Jane Doe",
  "address": {
    "apartmentNumber": 22,
    "street": {
      "streetName": "Bourbon Street",
      "streetNumber": 15
    }
  }
}


As you can see, all id fields from all sub-objects have been filtered. Also, you can filter fields from sub-objects separately.

Annotation Example

@FieldFilterSetting(className = User.class, fields = {"id", "password", "secretKey"})
@FieldFilterSetting(className = Address.class, fields = {"apartmentNumber"})
@FieldFilterSetting(className = Street.class, fields = {"streetNumber"})


Response Result

{
  "email": "janedoe@gmail.com", 
  "fullName": "Jane Doe",
  "address": {
    "id": 15,
    "street": {
      "id": 155,
      "streetName": "Bourbon Street"
    }
  }
}


Session Strategy Filter

Sometimes, you need to filter fields depending on the recipient role (attribute in HTTP Session). As an example, you have two recipient roles: ADMIN and USER

USER Role Annotation Example

@SessionStrategy(attributeName = "ROLE", attributeValue = "USER", ignoreFields = {
            @FieldFilterSetting(className = User.class, fields = {"id", "password"})
    })


ADMIN Role Annotation Example

@SessionStrategy(attributeName = "ROLE", attributeValue = "ADMIN", ignoreFields = {
                @FieldFilterSetting(className = User.class, fields = {"id"})
    })


So, if the session has an attribute ROLE with value:

  1. USER — fields id, password will be removed from the response
  2. ADMIN — the field idwill be removed from the response

Thus, this filter gives flexibility and the possibility of selective filtering.

XML Schema-Based Filter

If you need to configure field filtration in a file, this filter can provide this feature.

Annotation Example

@FileFilterSetting(fileName = "filter_configuration.xml")


XML Configuration File

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE config PUBLIC
        "-//json/json-ignore mapping DTD 1.0//EN"
        "https://rkonovalov.github.io/json-ignore-schema-1.0.dtd">
<config>
    <controller class-name="com.example.SessionService">
        <strategy attribute-name="ROLE" attribute-value="USER">
            <filter class="com.example.User">
                <field name="password"/>
            </filter>
        </strategy>

    </controller>
</config>


Description of XML Tags

  • config — main tag
  • controller — in this TAG, you may set the class-name property. Set the Service class name where it is used in this configuration
  • strategy — this TAG is similar to the SessionStrategyannotation
  • filter  this TAG is similar to the FieldFilterSettingannotation

Note: configuration files could be changed after the application starts and the controller will correctly reload all changed files. Don’t worry about it!

Spring Controller Filtration

If you don’t want to add filter annotations to each Service Response, you can add an annotation on the whole Service Controller.

Annotation Example

@FileFilterSetting(fileName = "filter_configuration.xml")
@RestController
public class SessionService {
    @Autowired
    private UserController userController;  

    @RequestMapping(value = "/users/signIn",
            params = {"email", "password"}, method = RequestMethod.POST,
            consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
            produces = {MediaType.APPLICATION_JSON_VALUE})            
    public User signIn(@RequestParam("email") String email, @RequestParam("password") String password) {
        return userController.signInUser(email, password);
    }
}


You can declare all of the required filters.

Dynamic Filter

Using this type of filter, you can create your own filter with custom behavior. All dynamic filters should implement the DynamicFilterEvent.class, and the filter should be annotated by theDynamicFilterComponent annotation.

Dynamic Custom Filter Example

@DynamicFilterComponent
    public class DemoIdFilter implements DynamicFilterEvent {
        @Override
        public FilterFields onGetFilterFields(MethodParameter methodParameter, RequestSession request) {
            if(request.getSession().getAttribute("SOME_VALUE") != null) {
                return new FilterFields(User.class, Arrays.asList("id", "password", "email"));
            } else
                return new FilterFields();
        }
    }


In this example, we created the DemoIdFilter that attempts to find the attribute SOME_VALUE in an Http Session. If the attribute exists, the event onGetFilterFieldsreturns a configured FilterFields. As you can see, FilterFields contains a filter for the User.class. If the Response Body of the Spring Service contains an object instance of the User.class, it will be filtered and the fields id, password, email will be removed from the response.

Annotation Example

@DynamicFilter(DemoIdFilter.class)
    @RequestMapping(value = "/users/signIn",
            params = {"email", "password"}, method = RequestMethod.POST,
            consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
            produces = {MediaType.APPLICATION_JSON_VALUE})            
    public User signIn(@RequestParam("email") String email, @RequestParam("password") String password) {
        return userController.signInUser(email, password);
    }


With this filter, you get huge space for Service Response customization.

Check out these links to learn more:

  • JFilter Main page link
  • JFilter GitHub project page link

Conclusion

In the world of big data and complex systems, sometimes, we need to control and flexibly change data. Maybe this solution is not the best, but I hope it will save you some extra time. You can use it in your projects without the “rain dance” and still achieve your desired result.

Topics:
java, json, microservices, spring framework, tutorial, web development, web services, xml

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}